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;
}
}
+ 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 {
IOUtils.closeQuietly(input);
}
}
-
}
+++ /dev/null
-/*
- * 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);
-
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
--- /dev/null
+/*
+ * 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();
+
+}
--- /dev/null
+/*
+ * 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
+ );
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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));
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ * 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;
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;
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()) {
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;
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)
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')
drop_table('action_plans_reviews')
end
-end
\ No newline at end of file
+end
+++ /dev/null
-/*
- * 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;
- }
- }
-}
+++ /dev/null
-/*
- * 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");
- }
-}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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]");
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+/*
+ * 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() {
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
+++ /dev/null
-<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
+++ /dev/null
-<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>
+++ /dev/null
--- 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
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+-- 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