diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-07-04 19:23:31 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-07-04 19:26:01 +0200 |
commit | ccb95087fd7aab08d2e8e057eecd21797ed7f9b6 (patch) | |
tree | 2bcb2aaccbe363ed93dea95e0d96e182692c264c | |
parent | a60604f5d250121fd594bb3d331a0f85fe889797 (diff) | |
download | sonarqube-ccb95087fd7aab08d2e8e057eecd21797ed7f9b6.tar.gz sonarqube-ccb95087fd7aab08d2e8e057eecd21797ed7f9b6.zip |
SONAR-4470 Performance issue when migrating DB from 3.5 to 3.6 (mainly with Oracle)
37 files changed, 1244 insertions, 458 deletions
@@ -685,6 +685,11 @@ <version>1.3.9</version> </dependency> <dependency> + <groupId>commons-dbutils</groupId> + <artifactId>commons-dbutils</artifactId> + <version>1.5</version> + </dependency> + <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties index 024dc35d67f..0e7f3a8157b 100644 --- a/sonar-application/src/main/assembly/conf/sonar.properties +++ b/sonar-application/src/main/assembly/conf/sonar.properties @@ -61,7 +61,7 @@ sonar.jdbc.url: jdbc:h2:tcp://localhost:9092/sonar #----- Oracle 10g/11g # To connect to Oracle database : # -# - It's recommended to use the latest version of the JDBC driver (either ojdbc6.jar for Java 6 or ojdbc5.jar for Java 5). +# - It's recommended to use the latest version of the JDBC driver (ojdbc6.jar). # Download it in http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html # - Copy the driver to the directory extensions/jdbc-driver/oracle/ # - Comment the embedded database and uncomment the following line : diff --git a/sonar-core/pom.xml b/sonar-core/pom.xml index c7c65fa9e66..7047fc7d230 100644 --- a/sonar-core/pom.xml +++ b/sonar-core/pom.xml @@ -61,6 +61,10 @@ <artifactId>commons-dbcp</artifactId> </dependency> <dependency> + <groupId>commons-dbutils</groupId> + <artifactId>commons-dbutils</artifactId> + </dependency> + <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-classworlds</artifactId> </dependency> diff --git a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java index 2e90c431243..81168944cf2 100644 --- a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java +++ b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java @@ -20,10 +20,10 @@ package org.sonar.core.measure; import com.google.common.base.Strings; +import org.apache.commons.dbutils.DbUtils; import org.apache.ibatis.session.SqlSession; import org.sonar.api.ServerComponent; import org.sonar.core.persistence.Database; -import org.sonar.core.persistence.DatabaseUtils; import org.sonar.core.persistence.MyBatis; import org.sonar.core.resource.ResourceDao; @@ -67,7 +67,7 @@ public class MeasureFilterExecutor implements ServerComponent { } finally { MyBatis.closeQuietly(session); // connection is supposed to be closed by the session - DatabaseUtils.closeQuietly(connection); + DbUtils.closeQuietly(connection); } return rows; diff --git a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java index 8545d98be8f..bed09124a09 100644 --- a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java +++ b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java @@ -22,10 +22,10 @@ package org.sonar.core.measure; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; +import org.apache.commons.dbutils.DbUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.sonar.core.persistence.Database; -import org.sonar.core.persistence.DatabaseUtils; import org.sonar.core.resource.SnapshotDto; import javax.annotation.Nullable; @@ -67,8 +67,8 @@ class MeasureFilterSql { return process(rs); } finally { - DatabaseUtils.closeQuietly(rs); - DatabaseUtils.closeQuietly(statement); + DbUtils.closeQuietly(rs); + DbUtils.closeQuietly(statement); } } diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java deleted file mode 100644 index aba584bcaa3..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.persistence; - -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -/** - * @since 2.13 - */ -public final class DatabaseUtils { - private DatabaseUtils() { - } - - /** - * List of all the tables. - * This list is hardcoded because we didn't succeed in using java.sql.DatabaseMetaData#getTables() in the same way - * for all the supported databases, particularly due to Oracle results. - */ - static final String[] TABLE_NAMES = { - "action_plans", - "active_dashboards", - "active_rules", - "active_rule_changes", - "active_rule_parameters", - "active_rule_param_changes", - "alerts", - "authors", - "characteristics", - "characteristic_edges", - "characteristic_properties", - "dashboards", - "dependencies", - "duplications_index", - "events", - "graphs", - "groups", - "groups_users", - "group_roles", - "issues", - "issue_changes", - "issue_filters", - "issue_filter_favourites", - "loaded_templates", - "manual_measures", - "measure_data", - "measure_filters", - "measure_filter_favourites", - "metrics", - "notifications", - "permission_templates", - "perm_templates_users", - "perm_templates_groups", - "projects", - "project_links", - "project_measures", - "properties", - "quality_models", - "resource_index", - "rules", - "rules_parameters", - "rules_profiles", - "semaphores", - "schema_migrations", - "snapshots", - "snapshot_sources", - "snapshot_data", - "users", - "user_roles", - "widgets", - "widget_properties"}; - - public static void closeQuietly(@Nullable Connection connection) { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close connection", e); - // ignore - } - } - } - - public static void closeQuietly(@Nullable Statement stmt) { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close statement", e); - // ignore - } - } - } - - public static void closeQuietly(@Nullable ResultSet rs) { - if (rs != null) { - try { - rs.close(); - } catch (SQLException e) { - LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close result set", e); - // ignore - } - } - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java index 16bde3cd397..e77f001bd6b 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -20,6 +20,7 @@ package org.sonar.core.persistence; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import org.apache.ibatis.session.SqlSession; import org.sonar.api.BatchComponent; import org.sonar.api.ServerComponent; @@ -38,6 +39,65 @@ public class DatabaseVersion implements BatchComponent, ServerComponent { UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL } + /** + * List of all the tables. + * This list is hardcoded because we didn't succeed in using java.sql.DatabaseMetaData#getTables() in the same way + * for all the supported databases, particularly due to Oracle results. + */ + public static final List<String> TABLES = ImmutableList.of( + "action_plans", + "active_dashboards", + "active_rules", + "active_rule_changes", + "active_rule_parameters", + "active_rule_param_changes", + "alerts", + "authors", + "characteristics", + "characteristic_edges", + "characteristic_properties", + "dashboards", + "dependencies", + "duplications_index", + "events", + "graphs", + "groups", + "groups_users", + "group_roles", + "issues", + "issue_changes", + "issue_filters", + "issue_filter_favourites", + "loaded_templates", + "manual_measures", + "measure_data", + "measure_filters", + "measure_filter_favourites", + "metrics", + "notifications", + "permission_templates", + "perm_templates_users", + "perm_templates_groups", + "projects", + "project_links", + "project_measures", + "properties", + "quality_models", + "resource_index", + "rules", + "rules_parameters", + "rules_profiles", + "semaphores", + "schema_migrations", + "snapshots", + "snapshot_sources", + "snapshot_data", + "users", + "user_roles", + "widgets", + "widget_properties" + ); + private MyBatis mybatis; public DatabaseVersion(MyBatis mybatis) { diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java b/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java index 52e2dbd1c41..2a1fb7d9d1b 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbutils.DbUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,12 +90,12 @@ public class DbTemplate implements ServerComponent { LOG.error("Fail to copy table " + table, e); throw new IllegalStateException("Fail to copy table " + table, e); } finally { - DatabaseUtils.closeQuietly(destStatement); - DatabaseUtils.closeQuietly(destResultSet); - DatabaseUtils.closeQuietly(destConnection); - DatabaseUtils.closeQuietly(sourceResultSet); - DatabaseUtils.closeQuietly(sourceStatement); - DatabaseUtils.closeQuietly(sourceConnection); + DbUtils.closeQuietly(destStatement); + DbUtils.closeQuietly(destResultSet); + DbUtils.closeQuietly(destConnection); + DbUtils.closeQuietly(sourceResultSet); + DbUtils.closeQuietly(sourceStatement); + DbUtils.closeQuietly(sourceConnection); } return this; @@ -158,9 +159,9 @@ public class DbTemplate implements ServerComponent { LOG.error("Fail to get row count for table " + table, e); throw new SonarException("Fail to get row count for table " + table, e); } finally { - DatabaseUtils.closeQuietly(resultSet); - DatabaseUtils.closeQuietly(statement); - DatabaseUtils.closeQuietly(connection); + DbUtils.closeQuietly(resultSet); + DbUtils.closeQuietly(statement); + DbUtils.closeQuietly(connection); } } @@ -175,8 +176,8 @@ public class DbTemplate implements ServerComponent { LOG.error("Fail to truncate table " + table, e); throw new SonarException("Fail to truncate table " + table, e); } finally { - DatabaseUtils.closeQuietly(statement); - DatabaseUtils.closeQuietly(connection); + DbUtils.closeQuietly(statement); + DbUtils.closeQuietly(connection); } return this; @@ -200,7 +201,7 @@ public class DbTemplate implements ServerComponent { LOG.error("Fail to createSchema local database schema", e); throw new SonarException("Fail to createSchema local database schema", e); } finally { - DatabaseUtils.closeQuietly(connection); + DbUtils.closeQuietly(connection); } return this; diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java index 94ac7774d43..0ea501c2083 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java @@ -48,7 +48,7 @@ public final class DdlUtils { executeScript(connection, "org/sonar/core/persistence/rows-" + dialect + ".sql"); } - private static void executeScript(Connection connection, String path) { + public static void executeScript(Connection connection, String path) { ScriptRunner scriptRunner = newScriptRunner(connection); try { scriptRunner.runScript(Resources.getResourceAsReader(path)); diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java b/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java index 3b8d5c5c2d6..5d0752f0d4d 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java @@ -22,6 +22,7 @@ package org.sonar.core.persistence; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; +import org.apache.commons.dbutils.DbUtils; import org.apache.commons.lang.StringUtils; import org.hibernate.cfg.Environment; import org.slf4j.Logger; @@ -141,7 +142,7 @@ public class DefaultDatabase implements Database { } catch (Exception e) { LOG.error("Can not connect to database. Please check connectivity and settings (see the properties prefixed by 'sonar.jdbc.').", e); } finally { - DatabaseUtils.closeQuietly(connection); + DbUtils.closeQuietly(connection); } } diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java b/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java index a6e1e1b5d05..4d208829db1 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java @@ -60,7 +60,7 @@ import java.util.Properties; import static org.junit.Assert.fail; public abstract class AbstractDaoTestCase { - private static Logger LOG = LoggerFactory.getLogger(AbstractDaoTestCase.class); + private static final Logger LOG = LoggerFactory.getLogger(AbstractDaoTestCase.class); private static Database database; private static DatabaseCommands databaseCommands; private static IDatabaseTester databaseTester; @@ -80,7 +80,7 @@ public abstract class AbstractDaoTestCase { if (hasDialect) { database = new DefaultDatabase(settings); } else { - database = new H2Database("h2Tests"); + database = new H2Database("h2Tests", true); } database.start(); LOG.info("Test Database: " + database); diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java index 2205ac573f5..21fb4c923c1 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java @@ -119,9 +119,14 @@ public abstract class DatabaseCommands { connection.setAutoCommit(false); Statement statement = connection.createStatement(); - for (String table : DatabaseUtils.TABLE_NAMES) { - statement.executeUpdate("TRUNCATE TABLE " + table); - connection.commit(); + for (String table : DatabaseVersion.TABLES) { + try { + statement.executeUpdate("TRUNCATE TABLE " + table); + connection.commit(); + } catch (Exception e) { + // ignore + connection.rollback(); + } } statement.close(); @@ -133,7 +138,7 @@ public abstract class DatabaseCommands { connection.setAutoCommit(false); Statement statement = connection.createStatement(); - for (String table : DatabaseUtils.TABLE_NAMES) { + for (String table : DatabaseVersion.TABLES) { try { ResultSet result = statement.executeQuery("SELECT CASE WHEN MAX(ID) IS NULL THEN 1 ELSE MAX(ID)+1 END FROM " + table); result.next(); diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java deleted file mode 100644 index 28e1ef948f7..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.persistence; - -import org.junit.Test; -import org.sonar.core.persistence.dialect.Oracle; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class DatabaseUtilsTest extends AbstractDaoTestCase { - - @Test - public void should_close_connection() throws SQLException { - Connection connection = getConnection(); - assertThat(isClosed(connection)).isFalse(); - - DatabaseUtils.closeQuietly(connection); - assertThat(isClosed(connection)).isTrue(); - } - - @Test - public void should_support_null_connection() { - DatabaseUtils.closeQuietly((Connection) null); - // no failure - } - - @Test - public void should_close_statement_and_resultset() throws SQLException { - Connection connection = getConnection(); - try { - PreparedStatement statement = connection.prepareStatement(selectDual()); - ResultSet rs = statement.executeQuery(); - - DatabaseUtils.closeQuietly(rs); - DatabaseUtils.closeQuietly(statement); - - assertThat(isClosed(statement)).isTrue(); - assertThat(isClosed(rs)).isTrue(); - } finally { - DatabaseUtils.closeQuietly(connection); - } - } - - @Test - public void should_not_fail_on_connection_errors() throws SQLException { - Connection connection = mock(Connection.class); - doThrow(new SQLException()).when(connection).close(); - - DatabaseUtils.closeQuietly(connection); - - // no failure - verify(connection).close(); // just to be sure - } - - @Test - public void should_not_fail_on_statement_errors() throws SQLException { - Statement statement = mock(Statement.class); - doThrow(new SQLException()).when(statement).close(); - - DatabaseUtils.closeQuietly(statement); - - // no failure - verify(statement).close(); // just to be sure - } - - @Test - public void should_not_fail_on_resulset_errors() throws SQLException { - ResultSet rs = mock(ResultSet.class); - doThrow(new SQLException()).when(rs).close(); - - DatabaseUtils.closeQuietly(rs); - - // no failure - verify(rs).close(); // just to be sure - } - - /** - * Connection.isClosed() has been introduced in java 1.6 - */ - private boolean isClosed(Connection c) { - try { - c.createStatement().execute(selectDual()); - return false; - } catch (Exception e) { - return true; - } - } - - /** - * Statement.isClosed() has been introduced in java 1.6 - */ - private boolean isClosed(Statement s) { - try { - s.execute("SELECT 1"); - return false; - } catch (Exception e) { - return true; - } - } - - /** - * ResultSet.isClosed() has been introduced in java 1.6 - */ - private boolean isClosed(ResultSet rs) { - try { - rs.next(); - return false; - } catch (Exception e) { - return true; - } - } - - private String selectDual() { - String sql = "SELECT 1"; - if (Oracle.ID.equals(getDatabase().getDialect().getId())) { - sql = "SELECT 1 FROM DUAL"; - } - return sql; - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java b/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java index 4536993eb1d..31c6619aea7 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java @@ -20,6 +20,7 @@ package org.sonar.core.persistence; import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbutils.DbUtils; import org.hibernate.cfg.Environment; import org.sonar.core.persistence.dialect.Dialect; import org.sonar.core.persistence.dialect.H2; @@ -38,18 +39,22 @@ import java.util.Properties; */ public class H2Database implements Database { private final String name; + private final boolean createSchema; private BasicDataSource datasource; /** * IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest} */ - public H2Database(String name) { + public H2Database(String name, boolean createSchema) { this.name = name; + this.createSchema = createSchema; } public H2Database start() { startDatabase(); - createSchema(); + if (createSchema) { + createSchema(); + } return this; } @@ -70,10 +75,24 @@ public class H2Database implements Database { try { connection = datasource.getConnection(); DdlUtils.createSchema(connection, "h2"); + } catch (SQLException e) { throw new IllegalStateException("Fail to create schema", e); } finally { - DatabaseUtils.closeQuietly(connection); + DbUtils.closeQuietly(connection); + } + } + + public void executeScript(String classloaderPath) { + Connection connection = null; + try { + connection = datasource.getConnection(); + DdlUtils.executeScript(connection, classloaderPath); + + } catch (SQLException e) { + throw new IllegalStateException("Fail to execute script: " + classloaderPath, e); + } finally { + DbUtils.closeQuietly(connection); } } diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java index 67703eda4f5..19ab908c98a 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java @@ -30,7 +30,7 @@ import java.sql.SQLException; import static org.fest.assertions.Assertions.assertThat; public class H2DatabaseTest { - H2Database db = new H2Database("sonar2"); + H2Database db = new H2Database("sonar2", true); @Before public void startDb() { diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java index d0455791383..3a3bfcad71a 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java @@ -41,7 +41,7 @@ public class MyBatisTest { @BeforeClass public static void start() { - database = new H2Database("sonar2"); + database = new H2Database("sonar2", true); database.start(); } diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java b/sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java new file mode 100644 index 00000000000..39d0bf63762 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java @@ -0,0 +1,293 @@ +/* + * 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.core.persistence; + +import com.google.common.collect.Maps; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.dbutils.DbUtils; +import org.apache.commons.dbutils.QueryRunner; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.text.StrSubstitutor; +import org.dbunit.Assertion; +import org.dbunit.DataSourceDatabaseTester; +import org.dbunit.DatabaseUnitException; +import org.dbunit.IDatabaseTester; +import org.dbunit.assertion.DiffCollectingFailureHandler; +import org.dbunit.assertion.Difference; +import org.dbunit.database.DatabaseConfig; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.CompositeDataSet; +import org.dbunit.dataset.IDataSet; +import org.dbunit.dataset.ReplacementDataSet; +import org.dbunit.dataset.xml.FlatXmlDataSet; +import org.dbunit.ext.mssql.InsertIdentityOperation; +import org.dbunit.operation.DatabaseOperation; +import org.junit.rules.ExternalResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.config.Settings; +import org.sonar.core.config.Logback; +import org.sonar.core.persistence.dialect.Dialect; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.fail; + +public class TestDatabase extends ExternalResource { + + private static final Logger LOG = LoggerFactory.getLogger(TestDatabase.class); + + private Database db; + private DatabaseCommands commands; + private IDatabaseTester tester; + private MyBatis myBatis; + private String schemaPath = null; + + + public TestDatabase schema(Class baseClass, String filename) { + String path = StringUtils.replaceChars(baseClass.getCanonicalName(), '.', '/'); + schemaPath = path + "/" + filename; + return this; + } + + @Override + protected void before() throws Throwable { + if (db == null) { + Settings settings = new Settings().setProperties(Maps.fromProperties(System.getProperties())); + if (settings.hasKey("orchestrator.configUrl")) { + loadOrchestratorSettings(settings); + } + for (String key : settings.getKeysStartingWith("sonar.jdbc")) { + LOG.info(key + ": " + settings.getString(key)); + } + boolean hasDialect = settings.hasKey("sonar.jdbc.dialect"); + if (hasDialect) { + db = new DefaultDatabase(settings); + } else { + db = new H2Database("h2Tests" + DigestUtils.md5Hex(StringUtils.defaultString(schemaPath)), schemaPath == null); + } + db.start(); + if (schemaPath != null) { + // will fail if not H2 + ((H2Database) db).executeScript(schemaPath); + } + LOG.info("Test Database: " + db); + + commands = DatabaseCommands.forDialect(db.getDialect()); + tester = new DataSourceDatabaseTester(db.getDataSource()); + + myBatis = new MyBatis(db, settings, new Logback()); + myBatis.start(); + } + commands.truncateDatabase(db.getDataSource()); + } + + + @Override + protected void after() { + if (db != null) { + db.stop(); + db = null; + commands = null; + myBatis = null; + } + } + + public Database database() { + return db; + } + + public Dialect dialect() { + return db.getDialect(); + } + + public DatabaseCommands commands() { + return commands; + } + + public MyBatis myBatis() { + return myBatis; + } + + public Connection openConnection() throws SQLException { + return db.getDataSource().getConnection(); + } + + public void executeUpdateSql(String sql) { + Connection connection = null; + try { + connection = openConnection(); + new QueryRunner().update(connection, sql); + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + sql); + } finally { + DbUtils.commitAndCloseQuietly(connection); + } + } + + public void prepareDbUnit(Class testClass, String... testNames) { + InputStream[] streams = new InputStream[testNames.length]; + try { + for (int i = 0; i < testNames.length; i++) { + String path = "/" + testClass.getName().replace('.', '/') + "/" + testNames[i]; + streams[i] = testClass.getResourceAsStream(path); + if (streams[i] == null) { + throw new IllegalStateException("DbUnit file not found: " + path); + } + } + + prepareDbUnit(streams); + commands.resetPrimaryKeys(db.getDataSource()); + } catch (SQLException e) { + throw translateException("Could not setup DBUnit data", e); + } finally { + for (InputStream stream : streams) { + IOUtils.closeQuietly(stream); + } + } + } + + private void prepareDbUnit(InputStream... dataSetStream) { + IDatabaseConnection connection = null; + try { + IDataSet[] dataSets = new IDataSet[dataSetStream.length]; + for (int i = 0; i < dataSetStream.length; i++) { + dataSets[i] = dbUnitDataSet(dataSetStream[i]); + } + tester.setDataSet(new CompositeDataSet(dataSets)); + connection = dbUnitConnection(); + new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, tester.getDataSet()); + } catch (Exception e) { + throw translateException("Could not setup DBUnit data", e); + } finally { + closeQuietly(connection); + } + } + + public void assertDbUnit(Class testClass, String filename, String... tables) { + IDatabaseConnection connection = null; + try { + connection = dbUnitConnection(); + IDataSet dataSet = connection.createDataSet(); + String path = "/" + testClass.getName().replace('.', '/') + "/" + filename; + IDataSet expectedDataSet = dbUnitDataSet(testClass.getResourceAsStream(path)); + for (String table : tables) { + DiffCollectingFailureHandler diffHandler = new DiffCollectingFailureHandler(); + + Assertion.assertEquals(expectedDataSet.getTable(table), dataSet.getTable(table), diffHandler); + // Evaluate the differences and ignore some column values + List diffList = diffHandler.getDiffList(); + for (Object o : diffList) { + Difference diff = (Difference) o; + if (!"[ignore]".equals(diff.getExpectedValue())) { + throw new DatabaseUnitException(diff.toString()); + } + } + } + } catch (DatabaseUnitException e) { + fail(e.getMessage()); + } catch (Exception e) { + throw translateException("Error while checking results", e); + } finally { + closeQuietly(connection); + } + } + + private IDataSet dbUnitDataSet(InputStream stream) { + try { + ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream)); + dataSet.addReplacementObject("[null]", null); + dataSet.addReplacementObject("[false]", Boolean.FALSE); + dataSet.addReplacementObject("[true]", Boolean.TRUE); + return dataSet; + } catch (Exception e) { + throw translateException("Could not read the dataset stream", e); + } + } + + private IDatabaseConnection dbUnitConnection() { + try { + IDatabaseConnection connection = tester.getConnection(); + connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, commands.getDbUnitFactory()); + return connection; + } catch (Exception e) { + throw translateException("Error while getting connection", e); + } + } + + private void closeQuietly(IDatabaseConnection connection) { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + // ignore + } + } + + private static RuntimeException translateException(String msg, Exception cause) { + RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage())); + runtimeException.setStackTrace(cause.getStackTrace()); + return runtimeException; + } + + private void loadOrchestratorSettings(Settings settings) throws URISyntaxException, IOException { + String url = settings.getString("orchestrator.configUrl"); + URI uri = new URI(url); + InputStream input = null; + try { + if (url.startsWith("file:")) { + File file = new File(uri); + input = FileUtils.openInputStream(file); + } else { + HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); + int responseCode = connection.getResponseCode(); + if (responseCode >= 400) { + throw new IllegalStateException("Fail to request: " + uri + ". Status code=" + responseCode); + } + + input = connection.getInputStream(); + + } + Properties props = new Properties(); + props.load(input); + settings.addProperties(props); + for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) { + String interpolatedValue = StrSubstitutor.replace(entry.getValue(), System.getenv(), "${", "}"); + settings.setProperty(entry.getKey(), interpolatedValue); + } + } finally { + IOUtils.closeQuietly(input); + } + } + +} diff --git a/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java b/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java index 097ff170350..7998f074bd7 100644 --- a/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java +++ b/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java @@ -63,7 +63,7 @@ public abstract class AbstractDbUnitTestCase { @Before public void startDatabase() throws SQLException { if (database == null) { - database = new H2Database("sonarHibernate"); + database = new H2Database("sonarHibernate", true); database.start(); databaseCommands = DatabaseCommands.forDialect(database.getDialect()); diff --git a/sonar-server/pom.xml b/sonar-server/pom.xml index 092a26276b4..bd83907bd0c 100644 --- a/sonar-server/pom.xml +++ b/sonar-server/pom.xml @@ -86,18 +86,22 @@ <artifactId>xstream</artifactId> </dependency> <dependency> - <groupId>commons-lang</groupId> - <artifactId>commons-lang</artifactId> - </dependency> - <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> </dependency> <dependency> + <groupId>commons-dbutils</groupId> + <artifactId>commons-dbutils</artifactId> + </dependency> + <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> </dependency> <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + </dependency> + <dependency> <groupId>org.picocontainer</groupId> <artifactId>picocontainer</artifactId> </dependency> diff --git a/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigration.java b/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigration.java new file mode 100644 index 00000000000..2e99e4b30bb --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigration.java @@ -0,0 +1,32 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.db; + +import org.sonar.core.persistence.Database; + +/** + * Java alternative of ActiveRecord::Migration. + * @since 3.7 + */ +public interface DatabaseMigration { + + void execute(Database db); + +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java b/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigrator.java index b15db5f3711..8df637b7313 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java +++ b/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigrator.java @@ -17,11 +17,15 @@ * 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.core.persistence; +package org.sonar.server.db; +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; @@ -33,8 +37,8 @@ import java.sql.Connection; */ public class DatabaseMigrator implements ServerComponent { - private MyBatis myBatis; - private Database database; + private final MyBatis myBatis; + private final Database database; public DatabaseMigrator(MyBatis myBatis, Database database) { this.myBatis = myBatis; @@ -56,13 +60,25 @@ public class DatabaseMigrator implements ServerComponent { session = myBatis.openSession(); connection = session.getConnection(); DdlUtils.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. - DatabaseUtils.closeQuietly(connection); + 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) { + e.printStackTrace(); + throw new IllegalStateException("Fail to execute database migration: " + className, e); } - return true; } } diff --git a/sonar-server/src/main/java/org/sonar/server/database/EmbeddedDatabase.java b/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java index d76e9ad577d..f3bb4486ba0 100644 --- a/sonar-server/src/main/java/org/sonar/server/database/EmbeddedDatabase.java +++ b/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java @@ -17,7 +17,7 @@ * 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.database; +package org.sonar.server.db; import org.apache.commons.lang.StringUtils; import org.h2.Driver; diff --git a/sonar-server/src/main/java/org/sonar/server/database/EmbeddedDatabaseFactory.java b/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabaseFactory.java index 7179cd2824d..6ab6122a3b2 100644 --- a/sonar-server/src/main/java/org/sonar/server/database/EmbeddedDatabaseFactory.java +++ b/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabaseFactory.java @@ -17,7 +17,7 @@ * 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.database; +package org.sonar.server.db; import org.sonar.api.config.Settings; import org.sonar.api.database.DatabaseProperties; diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/ConvertViolationsToIssues.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/ConvertViolationsToIssues.java new file mode 100644 index 00000000000..7613a7d1845 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/db/migrations/ConvertViolationsToIssues.java @@ -0,0 +1,365 @@ +/* + * 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.apache.commons.lang.time.DateUtils; +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.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * Used in the Active Record Migration 401 + */ +public class ConvertViolationsToIssues implements DatabaseMigration { + + private static int GROUP_SIZE = 500; + 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 { + 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 String insertSql; + private Date oneYearAgo = DateUtils.addYears(new Date(), -1); + 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) { + if (Oracle.ID.equals(database.getDialect().getId())) { + insertSql = "INSERT INTO issues(id, 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) " + + " VALUES (issues_seq.nextval, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + } else { + insertSql = "INSERT INTO issues(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) " + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + } + } + + + 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("projectId"); + if (componentId == null) { + continue; + } + String issueKey = UUID.randomUUID().toString(); + String status, severity, reporter = null; + boolean manualSeverity; + Date createdAt = Objects.firstNonNull((Date) row.get("createdAt"), oneYearAgo); + Date updatedAt; + Long reviewId = (Long) row.get("reviewId"); + if (reviewId == null) { + // violation without review + status = "OPEN"; + manualSeverity = false; + severity = (String) row.get("severity"); + updatedAt = createdAt; + } else { + // violation + review + String reviewStatus = (String) row.get("reviewStatus"); + status = ("OPEN".equals(reviewStatus) ? "CONFIRMED" : reviewStatus); + manualSeverity = Objects.firstNonNull((Boolean) row.get("reviewManualSeverity"), false); + severity = (String) row.get("reviewSeverity"); + updatedAt = Objects.firstNonNull((Date) row.get("reviewUpdatedAt"), oneYearAgo); + if ((Boolean) row.get("reviewManualViolation")) { + reporter = login((Long) row.get("reviewReporterId")); + } + + List<Map<String, Object>> comments = runner.query(readConnection, ReviewCommentsHandler.SQL + reviewId, new ReviewCommentsHandler()); + for (Map<String, Object> comment : comments) { + comment.put("issueKey", issueKey); + allComments.add(comment); + } + } + + Object[] params = new Object[20]; + params[0] = issueKey; + params[1] = componentId; + params[2] = row.get("rootProjectId"); + params[3] = row.get("ruleId"); + 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("reviewResolution"); + params[11] = row.get("checksum"); + params[12] = reporter; + params[13] = login((Long) row.get("reviewAssigneeId")); + params[14] = plan((Long) row.get("planId")); + params[15] = row.get("reviewData"); + 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("userId")); + if (login != null) { + Object[] params = new Object[6]; + params[0] = UUID.randomUUID().toString(); + params[1] = comment.get("issueKey"); + params[2] = login; + params[3] = comment.get("reviewText"); + params[4] = comment.get("createdAt"); + params[5] = comment.get("updatedAt"); + allParams.add(params); + } + } + if (!allParams.isEmpty()) { + runner.batch(writeConnection, "INSERT INTO issue_changes(kee, issue_key, user_login, change_type, change_data, created_at, updated_at) VALUES (?, ?, ?, 'comment', ?, ?, ?)", 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>> { + private static String SQL = "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, plan.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 plan on plan.review_id=rev.id " + + " where "; + + static { + for (int i = 0; i < GROUP_SIZE; i++) { + if (i > 0) { + SQL += " or "; + } + SQL += "rf.id=?"; + } + } + + private static 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("reviewId", getLong(rs, "reviewId")); + map.put("projectId", getLong(rs, "projectId")); + map.put("rootProjectId", getLong(rs, "rootProjectId")); + map.put("ruleId", getLong(rs, "ruleId")); + map.put("severity", Objects.firstNonNull(SEVERITIES.get(getInt(rs, "failureLevel")), "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("createdAt", rs.getTimestamp("createdAt")); + map.put("reviewResolution", rs.getString("reviewResolution")); + map.put("reviewSeverity", Objects.firstNonNull(rs.getString("reviewSeverity"), "MAJOR")); + map.put("reviewStatus", rs.getString("reviewStatus")); + map.put("reviewReporterId", getLong(rs, "reviewReporterId")); + map.put("reviewAssigneeId", getLong(rs, "reviewAssigneeId")); + map.put("reviewData", rs.getString("reviewData")); + map.put("reviewManualSeverity", rs.getBoolean("reviewManualSeverity")); + map.put("reviewUpdatedAt", rs.getTimestamp("reviewUpdatedAt")); + map.put("reviewManualViolation", rs.getBoolean("reviewManualViolation")); + map.put("planId", getLong(rs, "planId")); + return map; + } + } + + private static class ReviewCommentsHandler extends AbstractListHandler<Map<String, Object>> { + static 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("createdAt", rs.getTimestamp("createdAt")); + map.put("updatedAt", rs.getTimestamp("updatedAt")); + map.put("userId", getLong(rs, "userId")); + map.put("reviewText", rs.getString("reviewText")); + return map; + } + } + + @CheckForNull + static Long getLong(ResultSet rs, String columnName) throws SQLException { + long l = rs.getLong(columnName); + return rs.wasNull() ? null : l; + } + + @CheckForNull + static Double getDouble(ResultSet rs, String columnName) throws SQLException { + double d = rs.getDouble(columnName); + return rs.wasNull() ? null : d; + } + + @CheckForNull + static Integer getInt(ResultSet rs, String columnName) throws SQLException { + int i = rs.getInt(columnName); + return rs.wasNull() ? null : i; + } + + +} diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/package-info.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/package-info.java new file mode 100644 index 00000000000..63993d65144 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/db/migrations/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.db.migrations; + +import javax.annotation.ParametersAreNonnullByDefault;
\ No newline at end of file diff --git a/sonar-server/src/main/java/org/sonar/server/db/package-info.java b/sonar-server/src/main/java/org/sonar/server/db/package-info.java new file mode 100644 index 00000000000..79dcbe940a5 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/db/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.db; + +import javax.annotation.ParametersAreNonnullByDefault;
\ No newline at end of file diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index c80b904ad84..2d7014b3728 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -72,7 +72,8 @@ import org.sonar.server.charts.ChartFactory; import org.sonar.server.component.DefaultRubyComponentService; import org.sonar.server.configuration.Backup; import org.sonar.server.configuration.ProfilesManager; -import org.sonar.server.database.EmbeddedDatabaseFactory; +import org.sonar.server.db.DatabaseMigrator; +import org.sonar.server.db.EmbeddedDatabaseFactory; import org.sonar.server.issue.*; import org.sonar.server.notifications.NotificationCenter; import org.sonar.server.notifications.NotificationService; diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 5c2626bf14c..cd8a3f00a6d 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -46,7 +46,7 @@ import org.sonar.core.i18n.RuleI18nManager; import org.sonar.core.measure.MeasureFilterEngine; import org.sonar.core.measure.MeasureFilterResult; import org.sonar.core.persistence.Database; -import org.sonar.core.persistence.DatabaseMigrator; +import org.sonar.server.db.DatabaseMigrator; import org.sonar.core.persistence.DryRunDatabaseFactory; import org.sonar.core.purge.PurgeDao; import org.sonar.core.resource.ResourceIndexerDao; @@ -237,8 +237,8 @@ public final class JRubyFacade { return get(Database.class); } - public boolean createDatabase() { - return get(DatabaseMigrator.class).createDatabase(); + public DatabaseMigrator databaseMigrator() { + return get(DatabaseMigrator.class); } /* PROFILES CONSOLE : RULES AND METRIC THRESHOLDS */ diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/401_migrate_violations_to_issues.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/401_migrate_violations_to_issues.rb index f3b618c5f26..f1e7a5cd349 100644 --- a/sonar-server/src/main/webapp/WEB-INF/db/migrate/401_migrate_violations_to_issues.rb +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/401_migrate_violations_to_issues.rb @@ -24,145 +24,14 @@ # class MigrateViolationsToIssues < ActiveRecord::Migration - class RuleFailure < ActiveRecord::Base - end - - class Issue < ActiveRecord::Base - end - - class IssueChange < ActiveRecord::Base - end - - class User < ActiveRecord::Base - end - - class ActionPlan < ActiveRecord::Base - end - - PRIORITY_TO_SEVERITY = {1 => 'INFO', 2 => 'MINOR', 3 => 'MAJOR', 4 => 'CRITICAL', 5 => 'BLOCKER'} - def self.up - truncate_issues - - violation_ids = ActiveRecord::Base.connection.select_rows('select id from rule_failures') - - one_year_ago = Time.now.years_ago(1) - - say_with_time "Convert #{violation_ids.size} violations to issues" do - logins_by_id = User.all.inject({}) do |result, user| - result[user.id]=user.login - result - end - - plans_by_id = ActionPlan.all.inject({}) do |result, plan| - result[plan.id]=plan.kee - result - end - - violation_ids.each_slice(999) do |ids| - violations = ActiveRecord::Base.connection.select_rows(sql_select_violation(ids)) - ActiveRecord::Base.transaction do - violations.each do |violation| - issue_key = new_key - review_id = violation[0] - created_at = violation[7] || one_year_ago - resource_id = violation[1] - if resource_id.present? - issue = Issue.new( - :kee => issue_key, - :component_id => violation[1], - :rule_id => violation[2], - :severity => PRIORITY_TO_SEVERITY[violation[3].to_i] || 'MAJOR', - :message => violation[4], - :line => violation[5], - :effort_to_fix => violation[6], - :resolution => violation[13], - :checksum => violation[8], - :author_login => nil, - :issue_attributes => violation[15], - :issue_creation_date => created_at, - :issue_close_date => nil, - :created_at => created_at, - :root_component_id => violation[17] - ) - - if review_id.present? - # has review - status = violation[11] - manual_violation = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(violation[18]) - issue.status=(status=='OPEN' ? 'CONFIRMED' : status) - issue.issue_update_date=violation[16] || one_year_ago - issue.updated_at=violation[16] || one_year_ago - issue.severity=violation[12] || 'MAJOR' - issue.manual_severity=violation[14] - issue.reporter=logins_by_id[violation[9].to_i] if (violation[9].present? && manual_violation) - issue.assignee=logins_by_id[violation[10].to_i] if violation[10].present? - - plan_id = select_plan_id(review_id) - issue.action_plan_key=plans_by_id[plan_id.to_i] if plan_id - - review_comments = select_review_comments(review_id) - review_comments.each do |review_comment| - user_id = review_comment[2] - login = logins_by_id[user_id.to_i] - if login - IssueChange.create( - :kee => new_key, - :issue_key => issue_key, - :user_login => login, - :change_type => 'comment', - :change_data => review_comment[3], - :created_at => review_comment[0], - :updated_at => review_comment[1] - ) - end - end - - else - # does not have review - issue.status='OPEN' - issue.issue_update_date=created_at || one_year_ago - issue.updated_at=created_at || one_year_ago - issue.manual_severity=false - end - issue.save - end - end - end - end - end + Java::OrgSonarServerUi::JRubyFacade.getInstance().databaseMigrator().executeMigration('org.sonar.server.db.migrations.ConvertViolationsToIssues') + # Currently not possible in Java because of Oracle (triggers and sequences must be dropped) drop_table('rule_failures') drop_table('reviews') drop_table('review_comments') drop_table('action_plans_reviews') end - def self.truncate_issues - ActiveRecord::Base.connection.execute('truncate table issues') - ActiveRecord::Base.connection.execute('truncate table issue_changes') - end - - def self.sql_select_violation(ids) - "select rev.id, s.project_id, rf.rule_id, rf.failure_level, rf.message, rf.line, rf.cost, rf.created_at, - rf.checksum, - rev.user_id, rev.assignee_id, rev.status, rev.severity, rev.resolution, rev.manual_severity, rev.data, - rev.updated_at, s.root_project_id, rev.manual_violation - 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 - where rf.id in (#{ids.flatten.join(',')})" - end - - def self.new_key - Java::JavaUtil::UUID.randomUUID().toString() - end - - def self.select_plan_id(review_id) - ActiveRecord::Base.connection.select_value("select action_plan_id from action_plans_reviews where review_id=#{review_id}") - end - - def self.select_review_comments(review_id) - ActiveRecord::Base.connection.select_rows "select created_at, updated_at, user_id, review_text from review_comments where review_id=#{review_id}" - end end
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt b/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt index c9b1b28eb5a..a618f27923e 100644 --- a/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/README.txt @@ -5,8 +5,8 @@ HOW TO ADD A MIGRATION + sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl + sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql : - add "INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('<THE MIGRATION ID>')" -* Update the migration id defined in sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java -* If a table is added or removed, then edit sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java +* Update the migration id defined in the Java class org.sonar.core.persistence.DatabaseTest +* If a table is added or removed, then update the list org.sonar.core.persistence.DatabaseTest#TABLES diff --git a/sonar-server/src/main/webapp/WEB-INF/lib/database_version.rb b/sonar-server/src/main/webapp/WEB-INF/lib/database_version.rb index bba78a4e195..3984050e0c7 100644 --- a/sonar-server/src/main/webapp/WEB-INF/lib/database_version.rb +++ b/sonar-server/src/main/webapp/WEB-INF/lib/database_version.rb @@ -85,7 +85,7 @@ class DatabaseVersion end def self.try_restore_structure_dump() - ::Java::OrgSonarServerUi::JRubyFacade.getInstance().createDatabase() + ::Java::OrgSonarServerUi::JRubyFacade.getInstance().databaseMigrator().createDatabase() end def self.execute_sql_requests(requests) diff --git a/sonar-server/src/test/java/org/sonar/server/db/DatabaseMigratorTest.java b/sonar-server/src/test/java/org/sonar/server/db/DatabaseMigratorTest.java new file mode 100644 index 00000000000..7977ecfa717 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/db/DatabaseMigratorTest.java @@ -0,0 +1,84 @@ +/* + * 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.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.MySql; + +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(); + } + + public static class FakeMigration implements DatabaseMigration { + static boolean executed = false; + @Override + public void execute(Database db) { + executed = true; + } + } +} diff --git a/sonar-server/src/test/java/org/sonar/server/database/EmbeddedDatabaseTest.java b/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java index e2444269d11..0ab88116674 100644 --- a/sonar-server/src/test/java/org/sonar/server/database/EmbeddedDatabaseTest.java +++ b/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java @@ -17,7 +17,7 @@ * 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.database; +package org.sonar.server.db; import org.h2.Driver; diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest.java new file mode 100644 index 00000000000..c2ab498add3 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest.java @@ -0,0 +1,39 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.db.migrations; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.persistence.TestDatabase; + +public class ConvertViolationsToIssuesTest { + + @Rule + public TestDatabase db = new TestDatabase().schema(getClass(), "schema.sql"); + + @Test + public void convert_violations() throws Exception { + db.prepareDbUnit(getClass(), "convert_violations.xml"); + + new ConvertViolationsToIssues().execute(db.database()); + + db.assertDbUnit(getClass(), "convert_violations_result.xml", "issues", "issue_changes"); + } +} diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations.xml new file mode 100644 index 00000000000..5a97e0df76e --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations.xml @@ -0,0 +1,41 @@ +<dataset> + <snapshots id="10" root_project_id="10" project_id="11"/> + <users id="200" login="fabrice"/> + <users id="201" login="julien"/> + <action_plans id="999" kee="PLAN-999"/> + + <!-- violation without review --> + <rule_failures id="1" snapshot_id="10" rule_id="20" failure_level="2" message="the message" line="1234" cost="3.14" + created_at="2012-01-05" checksum="ABCDE" permanent_id="1"/> + + <!-- violation with review --> + <rule_failures id="2" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]" + created_at="2012-01-05" checksum="FGHIJ" permanent_id="2"/> + <reviews id="1" rule_failure_permanent_id="2" MANUAL_VIOLATION="[false]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18" + STATUS="OPEN" RESOLUTION="[null]" USER_ID="[null]" ASSIGNEE_ID="201"/> + + <review_comments ID="1" REVIEW_ID="1" USER_ID="200" REVIEW_TEXT="a comment" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/> + + <!-- comment by unknown user --> + <review_comments ID="2" REVIEW_ID="1" USER_ID="999" REVIEW_TEXT="to be ignored because unknown user" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/> + + <!-- manual violation --> + <rule_failures id="3" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]" + created_at="2012-01-05" checksum="FGHIJ" permanent_id="3"/> + <reviews id="2" rule_failure_permanent_id="3" MANUAL_VIOLATION="[true]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18" + STATUS="RESOLVED" RESOLUTION="FIXED" USER_ID="200" ASSIGNEE_ID="201"/> + + <action_plans_reviews review_id="1" action_plan_id="999"/> + + + + + + <!-- to be truncated --> + <issues ID="1" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]" + ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE" + CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05" + ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]" + RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/> + <issue_changes ID="1" ISSUE_KEY="ABCDE"/> +</dataset>
\ No newline at end of file diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations_result.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations_result.xml new file mode 100644 index 00000000000..8c0330bcb15 --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations_result.xml @@ -0,0 +1,21 @@ +<dataset> + <issues ID="2" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]" + ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE" + CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05" + ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]" + RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/> + + <issues ID="3" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="22" SEVERITY="BLOCKER" KEE="[ignore]" + ACTION_PLAN_KEY="PLAN-999" ASSIGNEE="julien" AUTHOR_LOGIN="[null]" CHECKSUM="FGHIJ" + CREATED_AT="2012-01-05" EFFORT_TO_FIX="[null]" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05" + ISSUE_UPDATE_DATE="2013-05-18" LINE="[null]" MANUAL_SEVERITY="[true]" MESSAGE="another message" REPORTER="[null]" + RESOLUTION="[null]" STATUS="CONFIRMED" UPDATED_AT="2013-05-18"/> + + <issues ID="4" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="22" SEVERITY="BLOCKER" KEE="[ignore]" + ACTION_PLAN_KEY="[null]" ASSIGNEE="julien" AUTHOR_LOGIN="[null]" CHECKSUM="FGHIJ" + CREATED_AT="2012-01-05" EFFORT_TO_FIX="[null]" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05" + ISSUE_UPDATE_DATE="2013-05-18" LINE="[null]" MANUAL_SEVERITY="[true]" MESSAGE="another message" REPORTER="fabrice" + RESOLUTION="FIXED" STATUS="RESOLVED" UPDATED_AT="2013-05-18"/> + + <issue_changes id="2" KEE="[ignore]" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/> +</dataset> diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/schema.sql b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/schema.sql new file mode 100644 index 00000000000..eb8d0511291 --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/schema.sql @@ -0,0 +1,152 @@ +-- 3.5 + +CREATE TABLE "SNAPSHOTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "CREATED_AT" TIMESTAMP, + "BUILD_DATE" TIMESTAMP, + "PROJECT_ID" INTEGER NOT NULL, + "PARENT_SNAPSHOT_ID" INTEGER, + "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U', + "PURGE_STATUS" INTEGER, + "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE, + "SCOPE" VARCHAR(3), + "QUALIFIER" VARCHAR(10), + "ROOT_SNAPSHOT_ID" INTEGER, + "VERSION" VARCHAR(500), + "PATH" VARCHAR(500), + "DEPTH" INTEGER, + "ROOT_PROJECT_ID" INTEGER, + "PERIOD1_MODE" VARCHAR(100), + "PERIOD1_PARAM" VARCHAR(100), + "PERIOD1_DATE" TIMESTAMP, + "PERIOD2_MODE" VARCHAR(100), + "PERIOD2_PARAM" VARCHAR(100), + "PERIOD2_DATE" TIMESTAMP, + "PERIOD3_MODE" VARCHAR(100), + "PERIOD3_PARAM" VARCHAR(100), + "PERIOD3_DATE" TIMESTAMP, + "PERIOD4_MODE" VARCHAR(100), + "PERIOD4_PARAM" VARCHAR(100), + "PERIOD4_DATE" TIMESTAMP, + "PERIOD5_MODE" VARCHAR(100), + "PERIOD5_PARAM" VARCHAR(100), + "PERIOD5_DATE" TIMESTAMP +); + +CREATE TABLE "USERS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "LOGIN" VARCHAR(40), + "NAME" VARCHAR(200), + "EMAIL" VARCHAR(100), + "CRYPTED_PASSWORD" VARCHAR(40), + "SALT" VARCHAR(40), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, + "REMEMBER_TOKEN" VARCHAR(500), + "REMEMBER_TOKEN_EXPIRES_AT" TIMESTAMP, + "ACTIVE" BOOLEAN DEFAULT TRUE +); + + +CREATE TABLE "RULE_FAILURES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "SNAPSHOT_ID" INTEGER NOT NULL, + "RULE_ID" INTEGER NOT NULL, + "FAILURE_LEVEL" INTEGER NOT NULL, + "MESSAGE" VARCHAR(4000), + "LINE" INTEGER, + "COST" DOUBLE, + "CREATED_AT" TIMESTAMP, + "CHECKSUM" VARCHAR(1000), + "PERMANENT_ID" INTEGER, + "SWITCHED_OFF" BOOLEAN, + "PERSON_ID" INTEGER +); + +CREATE TABLE "ACTION_PLANS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(100), + "NAME" VARCHAR(200), + "DESCRIPTION" VARCHAR(1000), + "DEADLINE" TIMESTAMP, + "USER_LOGIN" VARCHAR(40), + "PROJECT_ID" INTEGER, + "STATUS" VARCHAR(10), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + +CREATE TABLE "ACTION_PLANS_REVIEWS" ( + "ACTION_PLAN_ID" INTEGER, + "REVIEW_ID" INTEGER +); + +CREATE TABLE "REVIEWS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, + "USER_ID" INTEGER, + "ASSIGNEE_ID" INTEGER, + "TITLE" VARCHAR(500), + "STATUS" VARCHAR(10), + "SEVERITY" VARCHAR(10), + "RULE_FAILURE_PERMANENT_ID" INTEGER, + "PROJECT_ID" INTEGER, + "RESOURCE_ID" INTEGER, + "RESOURCE_LINE" INTEGER, + "RESOLUTION" VARCHAR(200), + "RULE_ID" INTEGER, + "MANUAL_VIOLATION" BOOLEAN NOT NULL, + "MANUAL_SEVERITY" BOOLEAN NOT NULL, + "DATA" VARCHAR(4000) +); + +CREATE TABLE "REVIEW_COMMENTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, + "REVIEW_ID" INTEGER, + "USER_ID" INTEGER, + "REVIEW_TEXT" VARCHAR(16777215) +); + + + +-- 3.6 + +CREATE TABLE "ISSUES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(50) UNIQUE NOT NULL, + "COMPONENT_ID" INTEGER NOT NULL, + "ROOT_COMPONENT_ID" INTEGER, + "RULE_ID" INTEGER, + "SEVERITY" VARCHAR(10), + "MANUAL_SEVERITY" BOOLEAN NOT NULL, + "MESSAGE" VARCHAR(4000), + "LINE" INTEGER, + "EFFORT_TO_FIX" DOUBLE, + "STATUS" VARCHAR(20), + "RESOLUTION" VARCHAR(20), + "CHECKSUM" VARCHAR(1000), + "REPORTER" VARCHAR(40), + "ASSIGNEE" VARCHAR(40), + "AUTHOR_LOGIN" VARCHAR(100), + "ACTION_PLAN_KEY" VARCHAR(50) NULL, + "ISSUE_ATTRIBUTES" VARCHAR(4000), + "ISSUE_CREATION_DATE" TIMESTAMP, + "ISSUE_CLOSE_DATE" TIMESTAMP, + "ISSUE_UPDATE_DATE" TIMESTAMP, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + +CREATE TABLE "ISSUE_CHANGES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(50), + "ISSUE_KEY" VARCHAR(50) NOT NULL, + "USER_LOGIN" VARCHAR(40), + "CHANGE_TYPE" VARCHAR(40), + "CHANGE_DATA" VARCHAR(16777215), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, +);
\ No newline at end of file |