aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-db/src/main/java/org/sonar/db/version
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-db/src/main/java/org/sonar/db/version')
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/AddColumnsBuilder.java146
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/BaseDataChange.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/BaseSqlStatement.java105
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/DataChange.java53
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/DatabaseMigration.java70
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/DdlChange.java67
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/DropColumnsBuilder.java72
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java97
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/MigrationStep.java32
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java108
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/Select.java167
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/SelectImpl.java98
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/SqlStatement.java42
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/Upsert.java33
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/UpsertImpl.java70
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v451/AddMissingCustomRuleParametersMigrationStep.java143
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v451/DeleteUnescapedActivities.java71
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/FeedFileSources.java283
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/FeedIssueLongDates.java71
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/FeedSnapshotSourcesUpdatedAt.java60
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/FileSourceDto.java212
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/InsertProjectsAuthorizationUpdatedAtMigrationStep.java63
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/PopulateProjectsUuidColumnsMigrationStep.java194
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/RemoveSortFieldFromIssueFiltersMigrationStep.java88
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/ReplaceIssueFiltersProjectKeyByUuid.java122
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/AddIssuesColumns.java92
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/AddNewCharacteristics.java330
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/CopyScmAccountsFromAuthorsToUsers.java172
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/DropIssuesColumns.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedAnalysisReportsLongDates.java68
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedEventsLongDates.java75
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedFileSourcesBinaryData.java163
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueChangesLongDates.java66
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueComponentUuids.java59
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueTags.java84
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssuesLongDates.java66
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedManualMeasuresLongDates.java66
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedSnapshotsLongDates.java66
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/FeedUsersLongDates.java78
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/RemovePermissionsOnModulesMigrationStep.java70
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/RenameComponentRelatedParamsInIssueFilters.java89
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v51/UpdateProjectsModuleUuidPath.java113
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/AddManualMeasuresComponentUuidColumn.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/FeedEventsComponentUuid.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/FeedFileSourcesDataType.java37
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/FeedManualMeasuresComponentUuid.java56
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/FeedMetricsBooleans.java43
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/FeedProjectLinksComponentUuid.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/MoveProjectProfileAssociation.java136
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/RemoveComponentLibraries.java56
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/RemoveDuplicatedComponentKeys.java163
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v52/RemoveSnapshotLibraries.java56
52 files changed, 4968 insertions, 0 deletions
diff --git a/sonar-db/src/main/java/org/sonar/db/version/AddColumnsBuilder.java b/sonar-db/src/main/java/org/sonar/db/version/AddColumnsBuilder.java
new file mode 100644
index 00000000000..ab6543fffe7
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/AddColumnsBuilder.java
@@ -0,0 +1,146 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.MsSql;
+import org.sonar.db.dialect.Oracle;
+import org.sonar.db.dialect.PostgreSql;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class AddColumnsBuilder {
+
+ private final Dialect dialect;
+ private final String tableName;
+ private List<ColumnDef> columnDefs = newArrayList();
+
+ public AddColumnsBuilder(Dialect dialect, String tableName) {
+ this.tableName = tableName;
+ this.dialect = dialect;
+ }
+
+ public AddColumnsBuilder addColumn(ColumnDef columnDef) {
+ columnDefs.add(columnDef);
+ return this;
+ }
+
+ public String build() {
+ StringBuilder sql = new StringBuilder().append("ALTER TABLE ").append(tableName).append(" ");
+ switch (dialect.getId()) {
+ case PostgreSql.ID:
+ addColumns(sql, "ADD COLUMN ");
+ break;
+ case MsSql.ID:
+ sql.append("ADD ");
+ addColumns(sql, "");
+ break;
+ default:
+ sql.append("ADD (");
+ addColumns(sql, "");
+ sql.append(")");
+ }
+ return sql.toString();
+ }
+
+ private void addColumns(StringBuilder sql, String columnPrefix) {
+ for (int i = 0; i < columnDefs.size(); i++) {
+ sql.append(columnPrefix);
+ addColumn(sql, columnDefs.get(i));
+ if (i < columnDefs.size() - 1) {
+ sql.append(", ");
+ }
+ }
+ }
+
+ private void addColumn(StringBuilder sql, ColumnDef columnDef) {
+ sql.append(columnDef.getName()).append(" ").append(typeToSql(columnDef));
+ Integer limit = columnDef.getLimit();
+ if (limit != null) {
+ sql.append(" (").append(Integer.toString(limit)).append(")");
+ }
+ sql.append(columnDef.isNullable() ? " NULL" : " NOT NULL");
+ }
+
+ private String typeToSql(ColumnDef columnDef) {
+ switch (columnDef.getType()) {
+ case STRING:
+ return "VARCHAR";
+ case BIG_INTEGER:
+ return !dialect.getId().equals(Oracle.ID) ? "BIGINT" : "NUMBER (38)";
+ default:
+ throw new IllegalArgumentException("Unsupported type : " + columnDef.getType());
+ }
+ }
+
+ public static class ColumnDef {
+ private String name;
+ private Type type;
+ private boolean isNullable;
+ private Integer limit;
+
+ public enum Type {
+ STRING, BIG_INTEGER
+ }
+
+ public ColumnDef setNullable(boolean isNullable) {
+ this.isNullable = isNullable;
+ return this;
+ }
+
+ public ColumnDef setLimit(@Nullable Integer limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public ColumnDef setName(String name) {
+ Preconditions.checkArgument(CharMatcher.JAVA_LOWER_CASE.or(CharMatcher.anyOf("_")).matchesAllOf(name), "Column name should only contains lowercase and _ characters");
+ this.name = name;
+ return this;
+ }
+
+ public ColumnDef setType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public boolean isNullable() {
+ return isNullable;
+ }
+
+ @CheckForNull
+ public Integer getLimit() {
+ return limit;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Type getType() {
+ return type;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/BaseDataChange.java b/sonar-db/src/main/java/org/sonar/db/version/BaseDataChange.java
new file mode 100644
index 00000000000..800e66a7e34
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/BaseDataChange.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.db.Database;
+
+public abstract class BaseDataChange implements DataChange, MigrationStep {
+
+ private final Database db;
+
+ public BaseDataChange(Database db) {
+ this.db = db;
+ }
+
+ @Override
+ public final void execute() throws SQLException {
+ Connection readConnection = null;
+ Connection writeConnection = null;
+ try {
+ readConnection = openConnection();
+
+ writeConnection = db.getDataSource().getConnection();
+ writeConnection.setAutoCommit(false);
+ Context context = new Context(db, readConnection, writeConnection);
+ execute(context);
+
+ } finally {
+ DbUtils.closeQuietly(readConnection);
+ DbUtils.closeQuietly(writeConnection);
+ }
+ }
+
+ /**
+ * Do not forget to close it !
+ */
+ protected Connection openConnection() throws SQLException {
+ Connection connection = db.getDataSource().getConnection();
+ connection.setAutoCommit(false);
+ if (connection.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)) {
+ connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
+ }
+ return connection;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/BaseSqlStatement.java b/sonar-db/src/main/java/org/sonar/db/version/BaseSqlStatement.java
new file mode 100644
index 00000000000..f302311bb2d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/BaseSqlStatement.java
@@ -0,0 +1,105 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.Date;
+import javax.annotation.Nullable;
+import org.apache.commons.dbutils.DbUtils;
+
+class BaseSqlStatement<CHILD extends SqlStatement> implements SqlStatement<CHILD> {
+ protected PreparedStatement pstmt;
+
+ protected BaseSqlStatement(PreparedStatement pstmt) {
+ this.pstmt = pstmt;
+ }
+
+ @Override
+ public CHILD close() {
+ DbUtils.closeQuietly(pstmt);
+ pstmt = null;
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setString(int columnIndex, @Nullable String value) throws SQLException {
+ pstmt.setString(columnIndex, value);
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setBytes(int columnIndex, @Nullable byte[] value) throws SQLException {
+ pstmt.setBytes(columnIndex, value);
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setInt(int columnIndex, @Nullable Integer value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.INTEGER);
+ } else {
+ pstmt.setInt(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setLong(int columnIndex, @Nullable Long value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.BIGINT);
+ } else {
+ pstmt.setLong(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setBoolean(int columnIndex, @Nullable Boolean value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.BOOLEAN);
+ } else {
+ pstmt.setBoolean(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setDouble(int columnIndex, @Nullable Double value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.DECIMAL);
+ } else {
+ pstmt.setDouble(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setDate(int columnIndex, @Nullable Date value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.TIMESTAMP);
+ } else {
+ pstmt.setTimestamp(columnIndex, new Timestamp(value.getTime()));
+ }
+ return (CHILD) this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/DataChange.java b/sonar-db/src/main/java/org/sonar/db/version/DataChange.java
new file mode 100644
index 00000000000..d06e4d7b093
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/DataChange.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+
+public interface DataChange {
+
+ class Context {
+ private final Database db;
+ private final Connection readConnection;
+ private final Connection writeConnection;
+
+ public Context(Database db, Connection readConnection, Connection writeConnection) {
+ this.db = db;
+ this.readConnection = readConnection;
+ this.writeConnection = writeConnection;
+ }
+
+ public Select prepareSelect(String sql) throws SQLException {
+ return SelectImpl.create(db, readConnection, sql);
+ }
+
+ public Upsert prepareUpsert(String sql) throws SQLException {
+ return UpsertImpl.create(writeConnection, sql);
+ }
+
+ public MassUpdate prepareMassUpdate() throws SQLException {
+ return new MassUpdate(db, readConnection, writeConnection);
+ }
+ }
+
+ void execute(Context context) throws SQLException;
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseMigration.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseMigration.java
new file mode 100644
index 00000000000..1d00155e13b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseMigration.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.util.Date;
+import javax.annotation.CheckForNull;
+
+public interface DatabaseMigration {
+ enum Status {
+ NONE, RUNNING, FAILED, SUCCEEDED
+ }
+
+ /**
+ * Starts the migration status and returns immediately.
+ * <p>
+ * Migration can not be started twice but calling this method wont raise an error.
+ * On the other hand, calling this method when no migration is needed will start the process anyway.
+ * </p>
+ * <p>
+ * <strong>This method should be named {@code start} but it can not be because it will be called by the pico container
+ * and this will cause unwanted behavior</strong>
+ * </p>
+ */
+ void startIt();
+
+ /**
+ * The time and day the last migration was started.
+ * <p>
+ * If no migration was ever started, the returned date is {@code null}. This value is reset when {@link #startIt()} is
+ * called.
+ * </p>
+ *
+ * @return a {@link Date} or {@code null}
+ */
+ @CheckForNull
+ Date startedAt();
+
+ /**
+ * Current status of the migration.
+ */
+ Status status();
+
+ /**
+ * The error of the last migration if it failed.
+ * <p>
+ * This value is reset when {@link #startIt()} is called.
+ * </p>
+ * @return a {@link Throwable} or {@code null}
+ */
+ @CheckForNull
+ Throwable failureError();
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/DdlChange.java b/sonar-db/src/main/java/org/sonar/db/version/DdlChange.java
new file mode 100644
index 00000000000..1e2bcb47582
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/DdlChange.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.db.Database;
+
+public abstract class DdlChange implements MigrationStep {
+
+ private final Database db;
+
+ public DdlChange(Database db) {
+ this.db = db;
+ }
+
+ @Override
+ public final void execute() throws SQLException {
+ Connection writeConnection = null;
+ try {
+ writeConnection = db.getDataSource().getConnection();
+ writeConnection.setAutoCommit(false);
+ Context context = new Context(writeConnection);
+ execute(context);
+
+ } finally {
+ DbUtils.closeQuietly(writeConnection);
+ }
+ }
+
+ public static class Context {
+ private final Connection writeConnection;
+
+ public Context(Connection writeConnection) {
+ this.writeConnection = writeConnection;
+ }
+
+ public void execute(String sql) throws SQLException {
+ try {
+ UpsertImpl.create(writeConnection, sql).execute().commit();
+ } catch (Exception e) {
+ throw new IllegalStateException(String.format("Fail to execute %s", sql), e);
+ }
+ }
+ }
+
+ public abstract void execute(Context context) throws SQLException;
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/DropColumnsBuilder.java b/sonar-db/src/main/java/org/sonar/db/version/DropColumnsBuilder.java
new file mode 100644
index 00000000000..61a0fbc45c8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/DropColumnsBuilder.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.MsSql;
+import org.sonar.db.dialect.MySql;
+import org.sonar.db.dialect.Oracle;
+import org.sonar.db.dialect.PostgreSql;
+
+public class DropColumnsBuilder {
+
+ private final Dialect dialect;
+ private final String tableName;
+ private final String[] columns;
+
+ public DropColumnsBuilder(Dialect dialect, String tableName, String... columns) {
+ this.tableName = tableName;
+ this.dialect = dialect;
+ this.columns = columns;
+ }
+
+ public String build() {
+ StringBuilder sql = new StringBuilder().append("ALTER TABLE ").append(tableName).append(" ");
+ switch (dialect.getId()) {
+ case PostgreSql.ID:
+ case MySql.ID:
+ dropColumns(sql, "DROP COLUMN ");
+ break;
+ case MsSql.ID:
+ sql.append("DROP COLUMN ");
+ dropColumns(sql, "");
+ break;
+ case Oracle.ID:
+ sql.append("DROP (");
+ dropColumns(sql, "");
+ sql.append(")");
+ break;
+ default:
+ throw new IllegalStateException(String.format("Unsupported database '%s'", dialect.getId()));
+ }
+ return sql.toString();
+ }
+
+ private void dropColumns(StringBuilder sql, String columnPrefix) {
+ for (int i = 0; i < columns.length; i++) {
+ sql.append(columnPrefix);
+ sql.append(columns[i]);
+ if (i < columns.length - 1) {
+ sql.append(", ");
+ }
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java b/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java
new file mode 100644
index 00000000000..a09ed828a5c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicLong;
+import org.sonar.core.util.ProgressLogger;
+import org.sonar.db.Database;
+
+public class MassUpdate {
+
+ public interface Handler {
+ /**
+ * Convert some column values of a given row.
+ *
+ * @return true if the row must be updated, else false. If false, then the update parameter must not be touched.
+ */
+ boolean handle(Select.Row row, SqlStatement update) throws SQLException;
+ }
+
+ private final Database db;
+ private final Connection readConnection;
+ private final Connection writeConnection;
+ private final AtomicLong counter = new AtomicLong(0L);
+ private final ProgressLogger progress = ProgressLogger.create(getClass(), counter);
+
+ private Select select;
+ private Upsert update;
+
+ MassUpdate(Database db, Connection readConnection, Connection writeConnection) {
+ this.db = db;
+ this.readConnection = readConnection;
+ this.writeConnection = writeConnection;
+ }
+
+ public SqlStatement select(String sql) throws SQLException {
+ this.select = SelectImpl.create(db, readConnection, sql);
+ return this.select;
+ }
+
+ public MassUpdate update(String sql) throws SQLException {
+ this.update = UpsertImpl.create(writeConnection, sql);
+ return this;
+ }
+
+ public MassUpdate rowPluralName(String s) {
+ this.progress.setPluralLabel(s);
+ return this;
+ }
+
+ public void execute(final Handler handler) throws SQLException {
+ if (select == null || update == null) {
+ throw new IllegalStateException("SELECT or UPDATE requests are not defined");
+ }
+
+ progress.start();
+ try {
+ select.scroll(new Select.RowHandler() {
+ @Override
+ public void handle(Select.Row row) throws SQLException {
+ if (handler.handle(row, update)) {
+ update.addBatch();
+ }
+ counter.getAndIncrement();
+ }
+ });
+ if (((UpsertImpl) update).getBatchCount() > 0L) {
+ update.execute().commit();
+ }
+ update.close();
+
+ // log the total number of processed rows
+ progress.log();
+ } finally {
+ progress.stop();
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/MigrationStep.java b/sonar-db/src/main/java/org/sonar/db/version/MigrationStep.java
new file mode 100644
index 00000000000..f34fdcffcd8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/MigrationStep.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.SQLException;
+
+/**
+ * Java alternative of ActiveRecord::Migration. Do not forget to declare implementation classes in {@link MigrationStepModule}
+ * @since 3.7
+ */
+public interface MigrationStep {
+
+ void execute() throws SQLException;
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
new file mode 100644
index 00000000000..4e1ea5cd811
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
@@ -0,0 +1,108 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import org.sonar.core.platform.Module;
+import org.sonar.db.version.v451.AddMissingCustomRuleParametersMigrationStep;
+import org.sonar.db.version.v451.DeleteUnescapedActivities;
+import org.sonar.db.version.v50.FeedFileSources;
+import org.sonar.db.version.v50.FeedIssueLongDates;
+import org.sonar.db.version.v50.FeedSnapshotSourcesUpdatedAt;
+import org.sonar.db.version.v50.InsertProjectsAuthorizationUpdatedAtMigrationStep;
+import org.sonar.db.version.v50.PopulateProjectsUuidColumnsMigrationStep;
+import org.sonar.db.version.v50.RemoveSortFieldFromIssueFiltersMigrationStep;
+import org.sonar.db.version.v50.ReplaceIssueFiltersProjectKeyByUuid;
+import org.sonar.db.version.v51.AddIssuesColumns;
+import org.sonar.db.version.v51.AddNewCharacteristics;
+import org.sonar.db.version.v51.CopyScmAccountsFromAuthorsToUsers;
+import org.sonar.db.version.v51.DropIssuesColumns;
+import org.sonar.db.version.v51.FeedAnalysisReportsLongDates;
+import org.sonar.db.version.v51.FeedEventsLongDates;
+import org.sonar.db.version.v51.FeedFileSourcesBinaryData;
+import org.sonar.db.version.v51.FeedIssueChangesLongDates;
+import org.sonar.db.version.v51.FeedIssueComponentUuids;
+import org.sonar.db.version.v51.FeedIssueTags;
+import org.sonar.db.version.v51.FeedIssuesLongDates;
+import org.sonar.db.version.v51.FeedManualMeasuresLongDates;
+import org.sonar.db.version.v51.FeedSnapshotsLongDates;
+import org.sonar.db.version.v51.FeedUsersLongDates;
+import org.sonar.db.version.v51.RemovePermissionsOnModulesMigrationStep;
+import org.sonar.db.version.v51.RenameComponentRelatedParamsInIssueFilters;
+import org.sonar.db.version.v51.UpdateProjectsModuleUuidPath;
+import org.sonar.db.version.v52.AddManualMeasuresComponentUuidColumn;
+import org.sonar.db.version.v52.FeedEventsComponentUuid;
+import org.sonar.db.version.v52.FeedFileSourcesDataType;
+import org.sonar.db.version.v52.FeedManualMeasuresComponentUuid;
+import org.sonar.db.version.v52.FeedMetricsBooleans;
+import org.sonar.db.version.v52.FeedProjectLinksComponentUuid;
+import org.sonar.db.version.v52.MoveProjectProfileAssociation;
+import org.sonar.db.version.v52.RemoveComponentLibraries;
+import org.sonar.db.version.v52.RemoveDuplicatedComponentKeys;
+import org.sonar.db.version.v52.RemoveSnapshotLibraries;
+
+public class MigrationStepModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ // 4.5.1
+ AddMissingCustomRuleParametersMigrationStep.class,
+ DeleteUnescapedActivities.class,
+
+ // 5.0
+ InsertProjectsAuthorizationUpdatedAtMigrationStep.class,
+ PopulateProjectsUuidColumnsMigrationStep.class,
+ ReplaceIssueFiltersProjectKeyByUuid.class,
+ FeedSnapshotSourcesUpdatedAt.class,
+ FeedFileSources.class,
+ FeedIssueLongDates.class,
+ RemoveSortFieldFromIssueFiltersMigrationStep.class,
+
+ // 5.1
+ FeedIssueTags.class,
+ FeedUsersLongDates.class,
+ RenameComponentRelatedParamsInIssueFilters.class,
+ CopyScmAccountsFromAuthorsToUsers.class,
+ FeedIssueChangesLongDates.class,
+ FeedAnalysisReportsLongDates.class,
+ UpdateProjectsModuleUuidPath.class,
+ FeedIssueComponentUuids.class,
+ FeedSnapshotsLongDates.class,
+ FeedIssuesLongDates.class,
+ FeedFileSourcesBinaryData.class,
+ FeedManualMeasuresLongDates.class,
+ FeedEventsLongDates.class,
+ AddNewCharacteristics.class,
+ RemovePermissionsOnModulesMigrationStep.class,
+ AddIssuesColumns.class,
+ DropIssuesColumns.class,
+
+ // 5.2
+ FeedProjectLinksComponentUuid.class,
+ FeedEventsComponentUuid.class,
+ MoveProjectProfileAssociation.class,
+ FeedFileSourcesDataType.class,
+ FeedMetricsBooleans.class,
+ AddManualMeasuresComponentUuidColumn.class,
+ FeedManualMeasuresComponentUuid.class,
+ RemoveSnapshotLibraries.class,
+ RemoveComponentLibraries.class,
+ RemoveDuplicatedComponentKeys.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/Select.java b/sonar-db/src/main/java/org/sonar/db/version/Select.java
new file mode 100644
index 00000000000..ab9433d25e6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/Select.java
@@ -0,0 +1,167 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.CheckForNull;
+
+public interface Select extends SqlStatement<Select> {
+
+ class Row {
+ private final ResultSet rs;
+
+ Row(ResultSet rs) {
+ this.rs = rs;
+ }
+
+ @CheckForNull
+ public Long getNullableLong(int columnIndex) throws SQLException {
+ long l = rs.getLong(columnIndex);
+ return rs.wasNull() ? null : l;
+ }
+
+ public long getLong(int columnIndex) throws SQLException {
+ return rs.getLong(columnIndex);
+ }
+
+ @CheckForNull
+ public Double getNullableDouble(int columnIndex) throws SQLException {
+ double d = rs.getDouble(columnIndex);
+ return rs.wasNull() ? null : d;
+ }
+
+ public double getDouble(int columnIndex) throws SQLException {
+ return rs.getDouble(columnIndex);
+ }
+
+ @CheckForNull
+ public Integer getNullableInt(int columnIndex) throws SQLException {
+ int i = rs.getInt(columnIndex);
+ return rs.wasNull() ? null : i;
+ }
+
+ public int getInt(int columnIndex) throws SQLException {
+ return rs.getInt(columnIndex);
+ }
+
+ @CheckForNull
+ public Boolean getNullableBoolean(int columnIndex) throws SQLException {
+ boolean b = rs.getBoolean(columnIndex);
+ return rs.wasNull() ? null : b;
+ }
+
+ public boolean getBoolean(int columnIndex) throws SQLException {
+ return rs.getBoolean(columnIndex);
+ }
+
+ @CheckForNull
+ public String getNullableString(int columnIndex) throws SQLException {
+ String s = rs.getString(columnIndex);
+ return rs.wasNull() ? null : s;
+ }
+
+ public String getString(int columnIndex) throws SQLException {
+ return rs.getString(columnIndex);
+ }
+
+ @CheckForNull
+ public Date getNullableDate(int columnIndex) throws SQLException {
+ Timestamp t = rs.getTimestamp(columnIndex);
+ return rs.wasNull() ? null : t;
+ }
+
+ public Date getDate(int columnIndex) throws SQLException {
+ return rs.getTimestamp(columnIndex);
+ }
+
+ @CheckForNull
+ public byte[] getNullableBytes(int columnIndex) throws SQLException {
+ byte[] b = rs.getBytes(columnIndex);
+ return rs.wasNull() ? null : b;
+ }
+
+ public byte[] getBytes(int columnIndex) throws SQLException {
+ return rs.getBytes(columnIndex);
+ }
+
+ @Override
+ public String toString() {
+ try {
+ ResultSetMetaData rsMetaData = rs.getMetaData();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i <= rsMetaData.getColumnCount(); i++) {
+ if (i > 1) {
+ sb.append(",");
+ }
+ sb.append(rsMetaData.getColumnLabel(i).toLowerCase());
+ sb.append("=");
+ sb.append(rs.getObject(i));
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ return "Unavailable: " + e.getMessage();
+ }
+ }
+ }
+
+ interface RowReader<T> {
+ T read(Row row) throws SQLException;
+ }
+
+ class LongReader implements RowReader<Long> {
+ private LongReader() {
+ }
+
+ @Override
+ public Long read(Row row) throws SQLException {
+ return row.getNullableLong(1);
+ }
+ }
+
+ RowReader<Long> LONG_READER = new LongReader();
+
+ class StringReader implements RowReader<String> {
+ private StringReader() {
+ }
+
+ @Override
+ public String read(Row row) throws SQLException {
+ return row.getNullableString(1);
+ }
+ }
+
+ RowReader<String> STRING_READER = new StringReader();
+
+ interface RowHandler {
+ void handle(Row row) throws SQLException;
+ }
+
+ <T> List<T> list(RowReader<T> reader) throws SQLException;
+
+ @CheckForNull
+ <T> T get(RowReader<T> reader) throws SQLException;
+
+ void scroll(RowHandler handler) throws SQLException;
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/SelectImpl.java b/sonar-db/src/main/java/org/sonar/db/version/SelectImpl.java
new file mode 100644
index 00000000000..90031326979
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/SelectImpl.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.db.Database;
+
+class SelectImpl extends BaseSqlStatement<Select> implements Select {
+
+ private SelectImpl(PreparedStatement pstmt) {
+ super(pstmt);
+ }
+
+ @Override
+ public <T> List<T> list(Select.RowReader<T> reader) throws SQLException {
+ ResultSet rs = pstmt.executeQuery();
+ Select.Row row = new Select.Row(rs);
+ try {
+ List<T> rows = new ArrayList<>();
+ while (rs.next()) {
+ rows.add(reader.read(row));
+ }
+ return rows;
+ } catch (Exception e) {
+ throw newExceptionWithRowDetails(row, e);
+ } finally {
+ DbUtils.closeQuietly(rs);
+ close();
+ }
+ }
+
+ @Override
+ public <T> T get(Select.RowReader<T> reader) throws SQLException {
+ ResultSet rs = pstmt.executeQuery();
+ Select.Row row = new Select.Row(rs);
+ try {
+ if (rs.next()) {
+ return reader.read(row);
+ }
+ return null;
+ } catch (Exception e) {
+ throw newExceptionWithRowDetails(row, e);
+ } finally {
+ DbUtils.closeQuietly(rs);
+ close();
+ }
+ }
+
+ @Override
+ public void scroll(Select.RowHandler handler) throws SQLException {
+ ResultSet rs = pstmt.executeQuery();
+ Select.Row row = new Select.Row(rs);
+ try {
+ while (rs.next()) {
+ handler.handle(row);
+ }
+ } catch (Exception e) {
+ throw newExceptionWithRowDetails(row, e);
+ } finally {
+ DbUtils.closeQuietly(rs);
+ close();
+ }
+ }
+
+ private IllegalStateException newExceptionWithRowDetails(Select.Row row, Exception e) {
+ return new IllegalStateException("Error during processing of row: [" + row + "]", e);
+ }
+
+ static SelectImpl create(Database db, Connection connection, String sql) throws SQLException {
+ // TODO use DbClient#newScrollingSelectStatement()
+ PreparedStatement pstmt = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ pstmt.setFetchSize(db.getDialect().getScrollDefaultFetchSize());
+ return new SelectImpl(pstmt);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/SqlStatement.java b/sonar-db/src/main/java/org/sonar/db/version/SqlStatement.java
new file mode 100644
index 00000000000..4418f659b16
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/SqlStatement.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.SQLException;
+import java.util.Date;
+import javax.annotation.Nullable;
+
+public interface SqlStatement<CHILD extends SqlStatement> {
+ CHILD setBoolean(int columnIndex, @Nullable Boolean value) throws SQLException;
+
+ CHILD setDate(int columnIndex, @Nullable Date value) throws SQLException;
+
+ CHILD setDouble(int columnIndex, @Nullable Double value) throws SQLException;
+
+ CHILD setInt(int columnIndex, @Nullable Integer value) throws SQLException;
+
+ CHILD setLong(int columnIndex, @Nullable Long value) throws SQLException;
+
+ CHILD setString(int columnIndex, @Nullable String value) throws SQLException;
+
+ CHILD setBytes(int columnIndex, @Nullable byte[] data) throws SQLException;
+
+ CHILD close();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/Upsert.java b/sonar-db/src/main/java/org/sonar/db/version/Upsert.java
new file mode 100644
index 00000000000..31068959a1b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/Upsert.java
@@ -0,0 +1,33 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.SQLException;
+
+/**
+ * INSERT, UPDATE or DELETE
+ */
+public interface Upsert extends SqlStatement<Upsert> {
+ Upsert addBatch() throws SQLException;
+
+ Upsert execute() throws SQLException;
+
+ Upsert commit() throws SQLException;
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/UpsertImpl.java b/sonar-db/src/main/java/org/sonar/db/version/UpsertImpl.java
new file mode 100644
index 00000000000..7f58429fac3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/UpsertImpl.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import org.sonar.db.BatchSession;
+
+public class UpsertImpl extends BaseSqlStatement<Upsert> implements Upsert {
+
+ private long batchCount = 0L;
+
+ private UpsertImpl(PreparedStatement pstmt) {
+ super(pstmt);
+ }
+
+ @Override
+ public Upsert addBatch() throws SQLException {
+ pstmt.addBatch();
+ pstmt.clearParameters();
+ batchCount++;
+ if (batchCount % BatchSession.MAX_BATCH_SIZE == 0L) {
+ pstmt.executeBatch();
+ pstmt.getConnection().commit();
+ }
+ return this;
+ }
+
+ @Override
+ public Upsert execute() throws SQLException {
+ if (batchCount == 0L) {
+ pstmt.execute();
+ } else {
+ pstmt.executeBatch();
+ }
+ return this;
+ }
+
+ public long getBatchCount() {
+ return batchCount;
+ }
+
+ @Override
+ public Upsert commit() throws SQLException {
+ pstmt.getConnection().commit();
+ return this;
+ }
+
+ static UpsertImpl create(Connection connection, String sql) throws SQLException {
+ return new UpsertImpl(connection.prepareStatement(sql));
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v451/AddMissingCustomRuleParametersMigrationStep.java b/sonar-db/src/main/java/org/sonar/db/version/v451/AddMissingCustomRuleParametersMigrationStep.java
new file mode 100644
index 00000000000..ec8aa9cdbe6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v451/AddMissingCustomRuleParametersMigrationStep.java
@@ -0,0 +1,143 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v451;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.ProgressLogger;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.version.MigrationStep;
+import org.sonar.db.version.v45.Migration45Mapper;
+import org.sonar.db.version.v45.Rule;
+import org.sonar.db.version.v45.RuleParameter;
+
+/**
+ * See http://jira.sonarsource.com/browse/SONAR-5575
+ *
+ * Add missing parameters (with no value) on each custom rules
+ *
+ * @since 4.5.1
+ */
+public class AddMissingCustomRuleParametersMigrationStep implements MigrationStep {
+
+ private final DbClient db;
+ private final System2 system;
+ private final AtomicLong counter = new AtomicLong(0L);
+
+ public AddMissingCustomRuleParametersMigrationStep(DbClient db, System2 system) {
+ this.db = db;
+ this.system = system;
+ }
+
+ @Override
+ public void execute() {
+ ProgressLogger progress = ProgressLogger.create(getClass(), counter);
+ progress.start();
+
+ DbSession session = db.openSession(false);
+ try {
+ Migration45Mapper mapper = session.getMapper(Migration45Mapper.class);
+
+ List<RuleParameter> templateRuleParams = mapper.selectAllTemplateRuleParameters();
+ Multimap<Integer, RuleParameter> templateRuleParamsByRuleId = ArrayListMultimap.create();
+ for (RuleParameter templateRuleParam : templateRuleParams) {
+ templateRuleParamsByRuleId.put(templateRuleParam.getRuleId(), templateRuleParam);
+ }
+
+ List<Rule> customRules = mapper.selectAllCustomRules();
+ Multimap<Integer, Integer> customRuleIdsByTemplateRuleId = HashMultimap.create();
+ for (Rule customRule : customRules) {
+ customRuleIdsByTemplateRuleId.put(customRule.getTemplateId(), customRule.getId());
+ }
+
+ List<RuleParameter> customRuleParams = mapper.selectAllCustomRuleParameters();
+ Multimap<Integer, RuleParameter> customRuleParamsByRuleId = ArrayListMultimap.create();
+ for (RuleParameter customRuleParam : customRuleParams) {
+ customRuleParamsByRuleId.put(customRuleParam.getRuleId(), customRuleParam);
+ }
+
+ // For each parameters of template rules, verify that each custom rules has the parameter
+ for (Integer templateRuleId : templateRuleParamsByRuleId.keySet()) {
+ for (RuleParameter templateRuleParam : templateRuleParamsByRuleId.get(templateRuleId)) {
+ // Each custom rule should have this parameter
+ insertCustomRuleParameterIfNotAlreadyExisting(templateRuleParam, templateRuleId, customRuleIdsByTemplateRuleId, customRuleParamsByRuleId, session);
+ }
+ }
+
+ session.commit();
+
+ // log the total number of process rows
+ progress.log();
+ } finally {
+ session.close();
+ progress.stop();
+ }
+ }
+
+ private void insertCustomRuleParameterIfNotAlreadyExisting(RuleParameter templateRuleParam, Integer templateRuleId,
+ Multimap<Integer, Integer> customRuleIdsByTemplateRuleId,
+ Multimap<Integer, RuleParameter> customRuleParamsByRuleId,
+ DbSession session) {
+ for (Integer customRuleId : customRuleIdsByTemplateRuleId.get(templateRuleId)) {
+ if (!hasParameter(templateRuleParam.getName(), customRuleParamsByRuleId.get(customRuleId))) {
+ // Insert new custom rule parameter
+ session.getMapper(Migration45Mapper.class).insertRuleParameter(new RuleParameter()
+ .setRuleId(customRuleId)
+ .setRuleTemplateId(templateRuleId)
+ .setName(templateRuleParam.getName())
+ .setDescription(templateRuleParam.getDescription())
+ .setType(templateRuleParam.getType())
+ );
+
+ // Update updated at date of custom rule in order to allow E/S indexation
+ session.getMapper(Migration45Mapper.class).updateRuleUpdateAt(customRuleId, new Date(system.now()));
+
+ counter.getAndIncrement();
+ }
+ }
+ }
+
+ private boolean hasParameter(String parameter, Collection<RuleParameter> customRuleParams) {
+ return Iterables.any(customRuleParams, new MatchParameter(parameter));
+ }
+
+ private static class MatchParameter implements Predicate<RuleParameter> {
+ private final String parameter;
+
+ public MatchParameter(String parameter) {
+ this.parameter = parameter;
+ }
+
+ @Override
+ public boolean apply(@Nullable RuleParameter input) {
+ return input != null && input.getName().equals(parameter);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v451/DeleteUnescapedActivities.java b/sonar-db/src/main/java/org/sonar/db/version/v451/DeleteUnescapedActivities.java
new file mode 100644
index 00000000000..d22ba822f47
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v451/DeleteUnescapedActivities.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v451;
+
+import java.sql.SQLException;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * See http://jira.sonarsource.com/browse/SONAR-5758
+ *
+ * @since 4.5.1
+ */
+public class DeleteUnescapedActivities extends BaseDataChange {
+
+ public DeleteUnescapedActivities(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("select id,data_field from activities where log_type='QPROFILE'");
+ massUpdate.update("delete from activities where id=?");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ String csv = row.getNullableString(2);
+ if (isUnescaped(csv)) {
+ update.setLong(1, row.getNullableLong(1));
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ static boolean isUnescaped(@Nullable String csv) {
+ if (csv != null) {
+ String[] splits = StringUtils.split(csv, ';');
+ for (String split : splits) {
+ if (StringUtils.countMatches(split, "=") != 1) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/FeedFileSources.java b/sonar-db/src/main/java/org/sonar/db/version/v50/FeedFileSources.java
new file mode 100644
index 00000000000..5a4c3921bf5
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/FeedFileSources.java
@@ -0,0 +1,283 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v50;
+
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.util.Date;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select.Row;
+import org.sonar.db.version.Select.RowReader;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * Used in the Active Record Migration 714
+ *
+ * @since 5.0
+ */
+public class FeedFileSources extends BaseDataChange {
+
+ private static final String SELECT_FILES_AND_MEASURES_SQL = "SELECT " +
+ "p.uuid, " +
+ "f.uuid, " +
+ "ss.data, " +
+ "ss.updated_at, " +
+
+ // revisions_by_line
+ "m1.text_value, " +
+ "m1.measure_data, " +
+
+ // authors_by_line
+ "m2.text_value, " +
+ "m2.measure_data, " +
+
+ // dates_by_line
+ "m3.text_value, " +
+ "m3.measure_data, " +
+
+ // hits_by_line
+ "m4.text_value, " +
+ "m4.measure_data, " +
+
+ // cond_by_line
+ "m5.text_value, " +
+ "m5.measure_data, " +
+
+ // cover_cond_by_line
+ "m6.text_value, " +
+ "m6.measure_data, " +
+
+ // it_hits_by_line
+ "m7.text_value, " +
+ "m7.measure_data, " +
+
+ // it_cond_by_line
+ "m8.text_value, " +
+ "m8.measure_data, " +
+
+ // it_cover_cond_by_line
+ "m9.text_value, " +
+ "m9.measure_data, " +
+
+ // overall_hits_by_line
+ "m10.text_value, " +
+ "m10.measure_data, " +
+
+ // overall_cond_by_line
+ "m11.text_value, " +
+ "m11.measure_data, " +
+
+ // overall_cover_cond_by_line
+ "m12.text_value, " +
+ "m12.measure_data, " +
+
+ // duplication_data
+ "m13.text_value, " +
+ "m13.measure_data " +
+
+ "FROM snapshots s " +
+ "JOIN snapshot_sources ss " +
+ "ON s.id = ss.snapshot_id AND s.islast = ? " +
+ "JOIN projects p " +
+ "ON s.root_project_id = p.id " +
+ "JOIN projects f " +
+ "ON s.project_id = f.id " +
+ "LEFT JOIN file_sources fs " +
+ "ON fs.file_uuid = f.uuid " +
+ "LEFT JOIN project_measures m1 " +
+ "ON m1.snapshot_id = s.id AND m1.metric_id = ? " +
+ "LEFT JOIN project_measures m2 " +
+ "ON m2.snapshot_id = s.id AND m2.metric_id = ? " +
+ "LEFT JOIN project_measures m3 " +
+ "ON m3.snapshot_id = s.id AND m3.metric_id = ? " +
+ "LEFT JOIN project_measures m4 " +
+ "ON m4.snapshot_id = s.id AND m4.metric_id = ? " +
+ "LEFT JOIN project_measures m5 " +
+ "ON m5.snapshot_id = s.id AND m5.metric_id = ? " +
+ "LEFT JOIN project_measures m6 " +
+ "ON m6.snapshot_id = s.id AND m6.metric_id = ? " +
+ "LEFT JOIN project_measures m7 " +
+ "ON m7.snapshot_id = s.id AND m7.metric_id = ? " +
+ "LEFT JOIN project_measures m8 " +
+ "ON m8.snapshot_id = s.id AND m8.metric_id = ? " +
+ "LEFT JOIN project_measures m9 " +
+ "ON m9.snapshot_id = s.id AND m9.metric_id = ? " +
+ "LEFT JOIN project_measures m10 " +
+ "ON m10.snapshot_id = s.id AND m10.metric_id = ? " +
+ "LEFT JOIN project_measures m11 " +
+ "ON m11.snapshot_id = s.id AND m11.metric_id = ? " +
+ "LEFT JOIN project_measures m12 " +
+ "ON m12.snapshot_id = s.id AND m12.metric_id = ? " +
+ "LEFT JOIN project_measures m13 " +
+ "ON m13.snapshot_id = s.id AND m13.metric_id = ? " +
+ "WHERE " +
+ "f.enabled = ? " +
+ "AND f.scope = 'FIL' " +
+ "AND p.scope = 'PRJ' AND p.qualifier = 'TRK' " +
+ "AND fs.file_uuid IS NULL";
+
+ private static final class FileSourceBuilder implements MassUpdate.Handler {
+ private final long now;
+
+ public FileSourceBuilder(System2 system) {
+ now = system.now();
+ }
+
+ @Override
+ public boolean handle(Row row, SqlStatement update) throws SQLException {
+ String projectUuid = row.getNullableString(1);
+ String fileUuid = row.getNullableString(2);
+ String source = StringUtils.defaultIfBlank(row.getNullableString(3), "");
+ Date updatedAt = row.getNullableDate(4);
+ byte[] shortRevisions = row.getNullableBytes(5);
+ byte[] longRevisions = row.getNullableBytes(6);
+ byte[] shortAuthors = row.getNullableBytes(7);
+ byte[] longAuthors = row.getNullableBytes(8);
+ byte[] shortDates = row.getNullableBytes(9);
+ byte[] longDates = row.getNullableBytes(10);
+ byte[] shortUtHits = row.getNullableBytes(11);
+ byte[] longUtHits = row.getNullableBytes(12);
+ byte[] shortUtCond = row.getNullableBytes(13);
+ byte[] longUtCond = row.getNullableBytes(14);
+ byte[] shortUtCovCond = row.getNullableBytes(15);
+ byte[] longUtCovCond = row.getNullableBytes(16);
+ byte[] shortItHits = row.getNullableBytes(17);
+ byte[] longItHits = row.getNullableBytes(18);
+ byte[] shortItCond = row.getNullableBytes(19);
+ byte[] longItCond = row.getNullableBytes(20);
+ byte[] shortItCovCond = row.getNullableBytes(21);
+ byte[] longItCovCond = row.getNullableBytes(22);
+ byte[] shortOverallHits = row.getNullableBytes(23);
+ byte[] longOverallHits = row.getNullableBytes(24);
+ byte[] shortOverallCond = row.getNullableBytes(25);
+ byte[] longOverallCond = row.getNullableBytes(26);
+ byte[] shortOverallCovCond = row.getNullableBytes(27);
+ byte[] longOverallCovCond = row.getNullableBytes(28);
+ byte[] shortDuplicationData = row.getNullableBytes(29);
+ byte[] longDuplicationData = row.getNullableBytes(30);
+
+ String[] sourceData = new FileSourceDto(source,
+ ofNullableBytes(shortRevisions, longRevisions),
+ ofNullableBytes(shortAuthors, longAuthors),
+ ofNullableBytes(shortDates, longDates),
+ ofNullableBytes(shortUtHits, longUtHits),
+ ofNullableBytes(shortUtCond, longUtCond),
+ ofNullableBytes(shortUtCovCond, longUtCovCond),
+ ofNullableBytes(shortItHits, longItHits),
+ ofNullableBytes(shortItCond, longItCond),
+ ofNullableBytes(shortItCovCond, longItCovCond),
+ ofNullableBytes(shortOverallHits, longOverallHits),
+ ofNullableBytes(shortOverallCond, longOverallCond),
+ ofNullableBytes(shortOverallCovCond, longOverallCovCond),
+ ofNullableBytes(shortDuplicationData, longDuplicationData)
+ ).getSourceData();
+
+ update.setString(1, projectUuid)
+ .setString(2, fileUuid)
+ .setLong(3, now)
+ .setLong(4, updatedAt == null ? now : updatedAt.getTime())
+ .setString(5, sourceData[0])
+ .setString(6, sourceData[1])
+ .setString(7, "");
+
+ return true;
+ }
+ }
+
+ private static String ofNullableBytes(@Nullable byte[] shortBytes, @Nullable byte[] longBytes) {
+ byte[] result;
+ if (shortBytes == null) {
+ if (longBytes == null) {
+ return "";
+ } else {
+ result = longBytes;
+ }
+ } else {
+ result = shortBytes;
+ }
+ return new String(result, StandardCharsets.UTF_8);
+ }
+
+ private final System2 system;
+
+ public FeedFileSources(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ RowReader<Long> simpleLongReader = new RowReader<Long>() {
+ @Override
+ public Long read(Row row) throws SQLException {
+ Long longValue = row.getNullableLong(1);
+ return longValue == null ? Long.valueOf(0L) : longValue;
+ }
+ };
+ Long revisionMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'revisions_by_line'").get(simpleLongReader);
+ Long authorMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'authors_by_line'").get(simpleLongReader);
+ Long datesMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'last_commit_datetimes_by_line'").get(simpleLongReader);
+ Long utCoverageHitsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'coverage_line_hits_data'").get(simpleLongReader);
+ Long utConditionsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'conditions_by_line'").get(simpleLongReader);
+ Long utCoveredConditionsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'covered_conditions_by_line'").get(simpleLongReader);
+ Long itCoverageHitsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'it_coverage_line_hits_data'").get(simpleLongReader);
+ Long itConditionsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'it_conditions_by_line'").get(simpleLongReader);
+ Long itCoveredConditionsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'it_covered_conditions_by_line'").get(simpleLongReader);
+ Long overallCoverageHitsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'overall_coverage_line_hits_data'").get(simpleLongReader);
+ Long overallConditionsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'overall_conditions_by_line'").get(simpleLongReader);
+ Long overallCoveredConditionsByLineMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'overall_covered_conditions_by_line'").get(simpleLongReader);
+ Long duplicationDataMetricId = context.prepareSelect("SELECT id FROM metrics WHERE name = 'duplications_data'").get(simpleLongReader);
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select(SELECT_FILES_AND_MEASURES_SQL)
+ .setBoolean(1, true)
+ .setLong(2, zeroIfNull(revisionMetricId))
+ .setLong(3, zeroIfNull(authorMetricId))
+ .setLong(4, zeroIfNull(datesMetricId))
+ .setLong(5, zeroIfNull(utCoverageHitsByLineMetricId))
+ .setLong(6, zeroIfNull(utConditionsByLineMetricId))
+ .setLong(7, zeroIfNull(utCoveredConditionsByLineMetricId))
+ .setLong(8, zeroIfNull(itCoverageHitsByLineMetricId))
+ .setLong(9, zeroIfNull(itConditionsByLineMetricId))
+ .setLong(10, zeroIfNull(itCoveredConditionsByLineMetricId))
+ .setLong(11, zeroIfNull(overallCoverageHitsByLineMetricId))
+ .setLong(12, zeroIfNull(overallConditionsByLineMetricId))
+ .setLong(13, zeroIfNull(overallCoveredConditionsByLineMetricId))
+ .setLong(14, zeroIfNull(duplicationDataMetricId))
+ .setBoolean(15, true);
+
+ massUpdate.update("INSERT INTO file_sources" +
+ "(project_uuid, file_uuid, created_at, updated_at, data, line_hashes, data_hash)" +
+ "VALUES " +
+ "(?, ?, ?, ?, ?, ?, ?)");
+ massUpdate.rowPluralName("files");
+
+ massUpdate.execute(new FileSourceBuilder(system));
+ }
+
+ private static long zeroIfNull(@Nullable Long value) {
+ return value == null ? 0L : value.longValue();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/FeedIssueLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v50/FeedIssueLongDates.java
new file mode 100644
index 00000000000..c9fd8cfbebe
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/FeedIssueLongDates.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v50;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedIssueLongDates extends BaseDataChange {
+
+ private final System2 system;
+
+ public FeedIssueLongDates(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final long now = system.now();
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT i.id, i.created_at, i.updated_at FROM issues i WHERE created_at_ms IS NULL");
+ massUpdate.update("UPDATE issues SET created_at_ms=?, updated_at_ms=? WHERE id=?");
+ massUpdate.rowPluralName("issues");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long id = row.getNullableLong(1);
+ Date createdAt = row.getNullableDate(2);
+ Date updatedAt = row.getNullableDate(3);
+
+ if (createdAt == null) {
+ update.setLong(1, now);
+ } else {
+ update.setLong(1, Math.min(now, createdAt.getTime()));
+ }
+ if (updatedAt == null) {
+ update.setLong(2, now);
+ } else {
+ update.setLong(2, Math.min(now, updatedAt.getTime()));
+ }
+ update.setLong(3, id);
+ return true;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/FeedSnapshotSourcesUpdatedAt.java b/sonar-db/src/main/java/org/sonar/db/version/v50/FeedSnapshotSourcesUpdatedAt.java
new file mode 100644
index 00000000000..cc1e6859b5f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/FeedSnapshotSourcesUpdatedAt.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * Used in the Active Record Migration 712
+ *
+ * @since 5.0
+ */
+public class FeedSnapshotSourcesUpdatedAt extends BaseDataChange {
+
+ public FeedSnapshotSourcesUpdatedAt(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT ss.id, s.build_date " +
+ "FROM snapshot_sources ss " +
+ "INNER JOIN snapshots s ON s.id = ss.snapshot_id " +
+ "WHERE ss.updated_at IS NULL");
+ massUpdate.update("UPDATE snapshot_sources " +
+ "SET updated_at=? " +
+ "WHERE id=?");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ update.setDate(1, row.getNullableDate(2));
+ update.setLong(2, row.getNullableLong(1));
+ return true;
+ }
+ });
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/FileSourceDto.java b/sonar-db/src/main/java/org/sonar/db/version/v50/FileSourceDto.java
new file mode 100644
index 00000000000..ca1683ffa2d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/FileSourceDto.java
@@ -0,0 +1,212 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v50;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.stax2.XMLInputFactory2;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.text.CsvWriter;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+class FileSourceDto {
+
+ private static final String SPACE_CHARS = "\t\n\r ";
+
+ private Iterator<String> sourceSplitter;
+
+ private final Map<Integer, String> revisions;
+ private final Map<Integer, String> authors;
+ private final Map<Integer, String> dates;
+ private final Map<Integer, String> utHits;
+ private final Map<Integer, String> utConditions;
+ private final Map<Integer, String> utCoveredConditions;
+ private final Map<Integer, String> itHits;
+ private final Map<Integer, String> itConditions;
+ private final Map<Integer, String> itCoveredConditions;
+ private final Map<Integer, String> overallHits;
+ private final Map<Integer, String> overallConditions;
+ private final Map<Integer, String> overallCoveredConditions;
+ private final List<List<Block>> duplicationGroups;
+
+ FileSourceDto(String source, String revisions, String authors, String dates,
+ String utHits, String utConditions, String utCoveredConditions,
+ String itHits, String itConditions, String itCoveredConditions,
+ String overallHits, String overallConditions, String overallCoveredConditions, String duplicationData) {
+ sourceSplitter = Splitter.onPattern("\r?\n|\r").split(source).iterator();
+ this.revisions = KeyValueFormat.parseIntString(revisions);
+ this.authors = KeyValueFormat.parseIntString(authors);
+ this.dates = KeyValueFormat.parseIntString(dates);
+ this.utHits = KeyValueFormat.parseIntString(utHits);
+ this.utConditions = KeyValueFormat.parseIntString(utConditions);
+ this.utCoveredConditions = KeyValueFormat.parseIntString(utCoveredConditions);
+ this.itHits = KeyValueFormat.parseIntString(itHits);
+ this.itConditions = KeyValueFormat.parseIntString(itConditions);
+ this.itCoveredConditions = KeyValueFormat.parseIntString(itCoveredConditions);
+ this.overallHits = KeyValueFormat.parseIntString(overallHits);
+ this.overallConditions = KeyValueFormat.parseIntString(overallConditions);
+ this.overallCoveredConditions = KeyValueFormat.parseIntString(overallCoveredConditions);
+ this.duplicationGroups = StringUtils.isNotBlank(duplicationData) ? parseDuplicationData(duplicationData) : Collections.<List<Block>>emptyList();
+ }
+
+ String[] getSourceData() {
+ String highlighting = "";
+ String symbolRefs = "";
+ Map<Integer, String> duplicationsPerLine = computeDuplicationsPerLine(duplicationGroups);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int line = 0;
+ String sourceLine = null;
+ CsvWriter csv = CsvWriter.of(new OutputStreamWriter(output, UTF_8));
+ StringBuilder lineHashes = new StringBuilder();
+ while (sourceSplitter.hasNext()) {
+ line++;
+ sourceLine = sourceSplitter.next();
+ lineHashes.append(lineChecksum(sourceLine)).append("\n");
+ csv.values(revisions.get(line), authors.get(line), dates.get(line),
+ utHits.get(line), utConditions.get(line), utCoveredConditions.get(line),
+ itHits.get(line), itConditions.get(line), itCoveredConditions.get(line),
+ overallHits.get(line), overallConditions.get(line), overallCoveredConditions.get(line),
+ highlighting, symbolRefs, duplicationsPerLine.get(line), sourceLine);
+ }
+ csv.close();
+ return new String[] {new String(output.toByteArray(), UTF_8), lineHashes.toString()};
+ }
+
+ public static String lineChecksum(String line) {
+ String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
+ if (reducedLine.isEmpty()) {
+ return "";
+ }
+ return DigestUtils.md5Hex(reducedLine);
+ }
+
+ private Map<Integer, String> computeDuplicationsPerLine(List<List<Block>> duplicationGroups) {
+ Map<Integer, String> result = new HashMap<>();
+ if (duplicationGroups.isEmpty()) {
+ return result;
+ }
+ Map<Integer, StringBuilder> dupPerLine = new HashMap<>();
+ int blockId = 1;
+ for (List<Block> group : duplicationGroups) {
+ Block originBlock = group.get(0);
+ addBlock(blockId, originBlock, dupPerLine);
+ blockId++;
+ for (int i = 1; i < group.size(); i++) {
+ Block duplicate = group.get(i);
+ if (duplicate.resourceKey.equals(originBlock.resourceKey)) {
+ addBlock(blockId, duplicate, dupPerLine);
+ blockId++;
+ }
+ }
+ }
+ for (Map.Entry<Integer, StringBuilder> entry : dupPerLine.entrySet()) {
+ result.put(entry.getKey(), entry.getValue().toString());
+ }
+ return result;
+ }
+
+ private void addBlock(int blockId, Block block, Map<Integer, StringBuilder> dupPerLine) {
+ int currentLine = block.start;
+ for (int i = 0; i < block.length; i++) {
+ if (dupPerLine.get(currentLine) == null) {
+ dupPerLine.put(currentLine, new StringBuilder());
+ }
+ if (dupPerLine.get(currentLine).length() > 0) {
+ dupPerLine.get(currentLine).append(',');
+ }
+ dupPerLine.get(currentLine).append(blockId);
+ currentLine++;
+ }
+
+ }
+
+ /**
+ * Parses data of {@link CoreMetrics#DUPLICATIONS_DATA}.
+ */
+ private static List<List<Block>> parseDuplicationData(String data) {
+ ImmutableList.Builder<List<Block>> groups = ImmutableList.builder();
+ try {
+ StringReader reader = new StringReader(data);
+ SMInputFactory inputFactory = initStax();
+ SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
+ // <duplications>
+ rootC.advance();
+ SMInputCursor groupsCursor = rootC.childElementCursor("g");
+ while (groupsCursor.getNext() != null) {
+ // <g>
+ SMInputCursor blocksCursor = groupsCursor.childElementCursor("b");
+ ImmutableList.Builder<Block> group = ImmutableList.builder();
+ while (blocksCursor.getNext() != null) {
+ // <b>
+ String resourceKey = blocksCursor.getAttrValue("r");
+ int firstLine = getAttrIntValue(blocksCursor, "s");
+ int numberOfLines = getAttrIntValue(blocksCursor, "l");
+
+ group.add(new Block(resourceKey, firstLine, numberOfLines));
+ }
+ groups.add(group.build());
+ }
+ } catch (Exception e) {
+ // SONAR-6174 Ignore any issue while parsing duplication measure. There is nothing user can do and things will get solved after
+ // next analysis anyway
+ }
+ return groups.build();
+ }
+
+ private static int getAttrIntValue(SMInputCursor cursor, String attrName) throws XMLStreamException {
+ return cursor.getAttrIntValue(cursor.findAttrIndex(null, attrName));
+ }
+
+ private static SMInputFactory initStax() {
+ XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
+ return new SMInputFactory(xmlFactory);
+ }
+
+ private static class Block {
+
+ final String resourceKey;
+ final int start;
+ final int length;
+
+ public Block(String resourceKey, int s, int l) {
+ this.resourceKey = resourceKey;
+ this.start = s;
+ this.length = l;
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/InsertProjectsAuthorizationUpdatedAtMigrationStep.java b/sonar-db/src/main/java/org/sonar/db/version/v50/InsertProjectsAuthorizationUpdatedAtMigrationStep.java
new file mode 100644
index 00000000000..d088be4f190
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/InsertProjectsAuthorizationUpdatedAtMigrationStep.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * Used in the Active Record Migration 716
+ *
+ * @since 5.0
+ */
+public class InsertProjectsAuthorizationUpdatedAtMigrationStep extends BaseDataChange {
+
+ private final System2 system;
+
+ public InsertProjectsAuthorizationUpdatedAtMigrationStep(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT p.id FROM projects p WHERE p.scope=? AND p.enabled=? and p.authorization_updated_at IS NULL").setString(1, "PRJ").setBoolean(2, true);
+ massUpdate.update("UPDATE projects SET authorization_updated_at=? WHERE id=?");
+ massUpdate.rowPluralName("projects");
+ massUpdate.execute(new MigrationHandler());
+ }
+
+ private class MigrationHandler implements MassUpdate.Handler {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long id = row.getNullableLong(1);
+ update.setLong(1, system.now());
+ update.setLong(2, id);
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/PopulateProjectsUuidColumnsMigrationStep.java b/sonar-db/src/main/java/org/sonar/db/version/v50/PopulateProjectsUuidColumnsMigrationStep.java
new file mode 100644
index 00000000000..181d0aeebf3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/PopulateProjectsUuidColumnsMigrationStep.java
@@ -0,0 +1,194 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.internal.Uuids;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.ProgressLogger;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.version.MigrationStep;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+
+/**
+ * Used in the Active Record Migration 705
+ *
+ * @since 5.0
+ */
+public class PopulateProjectsUuidColumnsMigrationStep implements MigrationStep {
+
+ private static final Logger LOG = Loggers.get(PopulateProjectsUuidColumnsMigrationStep.class);
+
+ private final DbClient db;
+ private final AtomicLong counter = new AtomicLong(0L);
+
+ public PopulateProjectsUuidColumnsMigrationStep(DbClient db) {
+ this.db = db;
+ }
+
+ @Override
+ public void execute() {
+ ProgressLogger progress = ProgressLogger.create(getClass(), counter);
+ progress.start();
+
+ final DbSession readSession = db.openSession(false);
+ final DbSession writeSession = db.openSession(true);
+ try {
+ readSession.select("org.sonar.db.version.v50.Migration50Mapper.selectRootProjects", new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext context) {
+ Component project = (Component) context.getResultObject();
+ List<Component> components = readSession.getMapper(Migration50Mapper.class).selectComponentChildrenForProjects(project.getId());
+ MigrationContext migrationContext = new MigrationContext(readSession, writeSession, project, components);
+ migrateEnabledComponents(migrationContext);
+ migrateDisabledComponents(migrationContext);
+ }
+ });
+ writeSession.commit(true);
+ readSession.commit(true);
+
+ migrateComponentsWithoutUuid(readSession, writeSession);
+ writeSession.commit(true);
+
+ // log the total number of process rows
+ progress.log();
+ } finally {
+ readSession.close();
+ writeSession.close();
+ progress.stop();
+ }
+ }
+
+ private void migrateEnabledComponents(MigrationContext migrationContext) {
+ saveComponent(migrationContext.writeSession, migrationContext.project);
+ for (Component component : migrationContext.componentsToMigrate) {
+ migrationContext.updateComponent(component);
+ if (Strings.isNullOrEmpty(component.getModuleUuidPath())) {
+ LOG.warn(String.format("Ignoring component id '%s' because the module uuid path could not be created", component.getId()));
+ } else {
+ migrationContext.updateComponent(component);
+ saveComponent(migrationContext.writeSession, component);
+ }
+ }
+ }
+
+ private void migrateDisabledComponents(MigrationContext migrationContext) {
+ for (Component component : migrationContext.readSession.getMapper(Migration50Mapper.class).selectDisabledDirectComponentChildrenForProjects(migrationContext.project.getId())) {
+ migrationContext.updateComponent(component);
+ saveComponent(migrationContext.writeSession, component);
+ }
+ for (Component component : migrationContext.readSession.getMapper(Migration50Mapper.class).selectDisabledNoneDirectComponentChildrenForProjects(
+ migrationContext.project.getId())) {
+ migrationContext.updateComponent(component);
+ saveComponent(migrationContext.writeSession, component);
+ }
+ }
+
+ private void migrateComponentsWithoutUuid(DbSession readSession, DbSession writeSession) {
+ for (Component component : readSession.getMapper(Migration50Mapper.class).selectComponentsWithoutUuid()) {
+ String uuid = Uuids.create();
+ component.setUuid(uuid);
+ component.setProjectUuid(uuid);
+ saveComponent(writeSession, component);
+ }
+ }
+
+ private void saveComponent(DbSession writeSession, Component component) {
+ writeSession.getMapper(Migration50Mapper.class).updateComponentUuids(component);
+ counter.getAndIncrement();
+ }
+
+ private static class MigrationContext {
+ private final DbSession readSession;
+ private final DbSession writeSession;
+ private final Component project;
+ private final Map<Long, Component> componentsBySnapshotId = newHashMap();
+ private final Map<Long, String> uuidByComponentId = newHashMap();
+ private final List<Component> componentsToMigrate = newArrayList();
+
+ private MigrationContext(DbSession readSession, DbSession writeSession, Component project, List<Component> components) {
+ this.readSession = readSession;
+ this.writeSession = writeSession;
+ this.project = project;
+
+ project.setUuid(getOrCreateUuid(project));
+ project.setProjectUuid(project.getUuid());
+
+ componentsBySnapshotId.put(project.getSnapshotId(), project);
+ for (Component component : components) {
+ componentsBySnapshotId.put(component.getSnapshotId(), component);
+ if (component.getUuid() == null) {
+ componentsToMigrate.add(component);
+ }
+ }
+ }
+
+ public void updateComponent(Component component) {
+ component.setUuid(getOrCreateUuid(component));
+ component.setProjectUuid(getOrCreateUuid(project));
+
+ String snapshotPath = component.getSnapshotPath();
+ StringBuilder moduleUuidPath = new StringBuilder();
+ String lastModuleUuid = null;
+ if (!Strings.isNullOrEmpty(snapshotPath)) {
+ for (String s : Splitter.on(".").omitEmptyStrings().split(snapshotPath)) {
+ Long snapshotId = Long.valueOf(s);
+ Component currentComponent = componentsBySnapshotId.get(snapshotId);
+ if (currentComponent != null && currentComponent.getScope().equals(Scopes.PROJECT)) {
+ lastModuleUuid = getOrCreateUuid(currentComponent);
+ moduleUuidPath.append(lastModuleUuid).append(".");
+ }
+ }
+ }
+
+ if (moduleUuidPath.length() > 0 && lastModuleUuid != null) {
+ // Remove last '.'
+ moduleUuidPath.deleteCharAt(moduleUuidPath.length() - 1);
+
+ component.setModuleUuidPath(moduleUuidPath.toString());
+ component.setModuleUuid(lastModuleUuid);
+ }
+ }
+
+ private String getOrCreateUuid(Component component) {
+ String existingUuid = component.getUuid();
+ String uuid = existingUuid == null ? uuidByComponentId.get(component.getId()) : existingUuid;
+ if (uuid == null) {
+ String newUuid = Uuids.create();
+ uuidByComponentId.put(component.getId(), newUuid);
+ return newUuid;
+ }
+ return uuid;
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/RemoveSortFieldFromIssueFiltersMigrationStep.java b/sonar-db/src/main/java/org/sonar/db/version/v50/RemoveSortFieldFromIssueFiltersMigrationStep.java
new file mode 100644
index 00000000000..50b1bd4c445
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/RemoveSortFieldFromIssueFiltersMigrationStep.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v50;
+
+import com.google.common.collect.Lists;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+// TODO could be refactored to benefit from existing code of ReplaceIssueFiltersProjectKeyByUuid
+// -> make any change of issue_filters easier
+public class RemoveSortFieldFromIssueFiltersMigrationStep extends BaseDataChange {
+
+ private static final char FIELD_SEPARATOR = '|';
+ private static final String SORT_KEY = "sort=";
+ private static final String ASC_KEY = "asc=";
+
+ private final System2 system;
+
+ public RemoveSortFieldFromIssueFiltersMigrationStep(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("select id,data from issue_filters where data like '%" + SORT_KEY + "%' or data like '%" + ASC_KEY + "%'");
+ massUpdate.update("update issue_filters set data=?, updated_at=? where id=?");
+ massUpdate.rowPluralName("issue filters");
+ massUpdate.execute(new FilterHandler(new Date(system.now())));
+ }
+
+ private static class FilterHandler implements MassUpdate.Handler {
+ private final Date now;
+
+ private FilterHandler(Date now) {
+ this.now = now;
+ }
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ String data = row.getNullableString(2);
+ String[] fields = StringUtils.split(data, FIELD_SEPARATOR);
+
+ boolean found = false;
+ List<String> fieldsToKeep = Lists.newArrayList();
+ for (String field : fields) {
+ if (field.startsWith(SORT_KEY) || field.startsWith(ASC_KEY)) {
+ found = true;
+ } else {
+ fieldsToKeep.add(field);
+ }
+ }
+ if (found) {
+ // data without 'sort' field
+ update.setString(1, StringUtils.join(fieldsToKeep, FIELD_SEPARATOR));
+ update.setDate(2, now);
+ update.setLong(3, row.getNullableLong(1));
+ }
+ return found;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/ReplaceIssueFiltersProjectKeyByUuid.java b/sonar-db/src/main/java/org/sonar/db/version/v50/ReplaceIssueFiltersProjectKeyByUuid.java
new file mode 100644
index 00000000000..16e2ca6a2f3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/ReplaceIssueFiltersProjectKeyByUuid.java
@@ -0,0 +1,122 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import javax.annotation.Nullable;
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * Used in the Active Record Migration 710
+ *
+ * @since 5.0
+ */
+public class ReplaceIssueFiltersProjectKeyByUuid extends BaseDataChange {
+
+ private final System2 system;
+
+ public ReplaceIssueFiltersProjectKeyByUuid(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(final Context context) throws SQLException {
+ final Date now = new Date(system.now());
+
+ Connection connection = null;
+ PreparedStatement pstmt = null;
+ try {
+ connection = openConnection();
+ pstmt = connection.prepareStatement("SELECT p.uuid as uuid FROM projects p WHERE p.kee=?");
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT f.id, f.data FROM issue_filters f WHERE f.data like '%componentRoots=%'");
+ massUpdate.update("UPDATE issue_filters SET data=?, updated_at=? WHERE id=?");
+ final PreparedStatement finalPstmt = pstmt;
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long id = row.getNullableLong(1);
+ String data = row.getNullableString(2);
+ if (data == null) {
+ return false;
+ }
+ update.setString(1, convertData(finalPstmt, data));
+ update.setDate(2, now);
+ update.setLong(3, id);
+ return true;
+ }
+ });
+ } finally {
+ DbUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(pstmt);
+ }
+ }
+
+ private String convertData(PreparedStatement pstmt, String data) throws SQLException {
+ StringBuilder newFields = new StringBuilder();
+ String[] fields = data.split("\\|");
+ for (int i = 0; i < fields.length; i++) {
+ String field = fields[i];
+ if (field.contains("componentRoots")) {
+ String[] componentRootValues = field.split("=");
+ append(pstmt, newFields, componentRootValues.length == 2 ? componentRootValues[1] : null);
+ } else {
+ newFields.append(field);
+ }
+ if (i < fields.length - 1) {
+ newFields.append("|");
+ }
+ }
+ return newFields.toString();
+ }
+
+ private static void append(PreparedStatement pstmt, StringBuilder newFields, @Nullable String projectKey) throws SQLException {
+ if (projectKey != null) {
+ pstmt.setString(1, projectKey);
+ ResultSet rs = null;
+ try {
+ rs = pstmt.executeQuery();
+ if (rs.next()) {
+ String projectUuid = DatabaseUtils.getString(rs, "uuid");
+ if (projectUuid != null) {
+ newFields.append("projectUuids=").append(projectUuid);
+ }
+ }
+ } finally {
+ DbUtils.closeQuietly(rs);
+ }
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/AddIssuesColumns.java b/sonar-db/src/main/java/org/sonar/db/version/v51/AddIssuesColumns.java
new file mode 100644
index 00000000000..09ffbdb94a8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/AddIssuesColumns.java
@@ -0,0 +1,92 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+import org.sonar.db.version.DdlChange;
+
+/**
+ * Add the following columns to the issues table :
+ * - issue_creation_date_ms
+ * - issue_update_date_ms
+ * - issue_close_date_ms
+ * - tags
+ * - component_uuid
+ * - project_uuid
+ */
+public class AddIssuesColumns extends DdlChange {
+
+ private final Database db;
+
+ public AddIssuesColumns(Database db) {
+ super(db);
+ this.db = db;
+ }
+
+ @Override
+ public void execute(DdlChange.Context context) throws SQLException {
+ context.execute(generateSql());
+ }
+
+ private String generateSql() {
+ return new AddColumnsBuilder(db.getDialect(), "issues")
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("issue_creation_date_ms")
+ .setType(AddColumnsBuilder.ColumnDef.Type.BIG_INTEGER)
+ .setNullable(true)
+ )
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("issue_update_date_ms")
+ .setType(AddColumnsBuilder.ColumnDef.Type.BIG_INTEGER)
+ .setNullable(true)
+ )
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("issue_close_date_ms")
+ .setType(AddColumnsBuilder.ColumnDef.Type.BIG_INTEGER)
+ .setNullable(true)
+ )
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("tags")
+ .setType(AddColumnsBuilder.ColumnDef.Type.STRING)
+ .setLimit(4000)
+ .setNullable(true))
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("component_uuid")
+ .setType(AddColumnsBuilder.ColumnDef.Type.STRING)
+ .setLimit(50)
+ .setNullable(true))
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("project_uuid")
+ .setType(AddColumnsBuilder.ColumnDef.Type.STRING)
+ .setLimit(50)
+ .setNullable(true))
+ .build();
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/AddNewCharacteristics.java b/sonar-db/src/main/java/org/sonar/db/version/v51/AddNewCharacteristics.java
new file mode 100644
index 00000000000..2623dcb75e0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/AddNewCharacteristics.java
@@ -0,0 +1,330 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v51;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.Select;
+
+/**
+ * See http://jira.sonarsource.com/browse/SONAR-6187
+ *
+ * Add a new Characteristic 'Usability' with 2 sub-characteristics 'Accessibility' and 'Ease of Use'
+ * and add a new sub-characteristic 'Compliance' for all characteristics.
+ *
+ * Nothing will be done if there's no characteristics in db, as they're all gonna be created by {@link org.sonar.server.startup.RegisterDebtModel}
+ *
+ * Before 4.3 the characteristics table contains requirements, then when selecting characteristics we should not forget to exclude them (with a filter on rule_id IS NULL)
+ *
+ */
+public class AddNewCharacteristics extends BaseDataChange {
+
+ private static final Logger LOGGER = Loggers.get(AddNewCharacteristics.class);
+
+ private static final String COMPLIANCE_NAME = "Compliance";
+ private static final String COMPLIANCE_KEY_SUFFIX = "_COMPLIANCE";
+
+ private static final String SECURITY_KEY = "SECURITY";
+ private static final String USABILITY_KEY = "USABILITY";
+
+ private static final String ERROR_SUFFIX = "Please restore your DB backup, start the previous version of SonarQube " +
+ "and update your SQALE model to fix this issue before trying again to run the migration.";
+
+ private final System2 system;
+
+ public AddNewCharacteristics(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ CharacteristicsContext characteristicsContext = new CharacteristicsContext(context, system);
+
+ // On an empty DB, there are no characteristics, they're all gonna be created after in RegisterDebtModel
+ if (!characteristicsContext.characteristics().isEmpty()) {
+ int usabilityOder = moveCharacteristicsDownToBeAbleToInsertUsability(characteristicsContext);
+ createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(characteristicsContext, usabilityOder);
+
+ createSubCharacteristic(characteristicsContext, "REUSABILITY" + COMPLIANCE_KEY_SUFFIX, "Reusability " + COMPLIANCE_NAME, "REUSABILITY");
+ createSubCharacteristic(characteristicsContext, "PORTABILITY" + COMPLIANCE_KEY_SUFFIX, "Portability " + COMPLIANCE_NAME, "PORTABILITY");
+ createSubCharacteristic(characteristicsContext, "MAINTAINABILITY" + COMPLIANCE_KEY_SUFFIX, "Maintainability " + COMPLIANCE_NAME, "MAINTAINABILITY");
+ createSubCharacteristic(characteristicsContext, SECURITY_KEY + COMPLIANCE_KEY_SUFFIX, "Security " + COMPLIANCE_NAME, SECURITY_KEY);
+ createSubCharacteristic(characteristicsContext, "EFFICIENCY" + COMPLIANCE_KEY_SUFFIX, "Efficiency " + COMPLIANCE_NAME, "EFFICIENCY");
+ createSubCharacteristic(characteristicsContext, "CHANGEABILITY" + COMPLIANCE_KEY_SUFFIX, "Changeability " + COMPLIANCE_NAME, "CHANGEABILITY");
+ createSubCharacteristic(characteristicsContext, "RELIABILITY" + COMPLIANCE_KEY_SUFFIX, "Reliability " + COMPLIANCE_NAME, "RELIABILITY");
+ createSubCharacteristic(characteristicsContext, "TESTABILITY" + COMPLIANCE_KEY_SUFFIX, "Testability " + COMPLIANCE_NAME, "TESTABILITY");
+ }
+ }
+
+ /**
+ * If the characteristic 'Security' exists, the new characteristic 'Usability' should be inserted just below it,
+ * so every existing characteristics below Security should move down.
+ *
+ * If the characteristic 'Security' does not exists, the new characteristic 'Usability' should be the first one,
+ * so every existing characteristics should move down.
+ *
+ * If the characteristic 'Usability' is already at the right place, nothing will be done.
+ */
+ private static int moveCharacteristicsDownToBeAbleToInsertUsability(CharacteristicsContext characteristicsContext) throws SQLException {
+ Characteristic security = characteristicsContext.findCharacteristicByKey(SECURITY_KEY);
+ Characteristic usability = characteristicsContext.findCharacteristicByKey(USABILITY_KEY);
+
+ int usabilityOder = 1;
+ int indexToStart = 0;
+ if (security != null) {
+ indexToStart = characteristicsContext.characteristics().indexOf(security) + 1;
+ usabilityOder = security.getOrder() + 1;
+ }
+
+ if (usability == null || usability.getOrder() != usabilityOder) {
+ // Move root characteristics one step lower
+ for (int i = indexToStart; i < characteristicsContext.characteristics().size(); i++) {
+ Characteristic characteristic = characteristicsContext.characteristics().get(i);
+ if (characteristic.getParentId() == null) {
+ characteristicsContext.updateCharacteristicOrder(characteristic.getKey(), characteristic.getOrder() + 1);
+ }
+ }
+ }
+ return usabilityOder;
+ }
+
+ private void createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(CharacteristicsContext characteristicsContext, int newUsabilityOrder)
+ throws SQLException {
+ String usabilityKey = USABILITY_KEY;
+ Characteristic usability = characteristicsContext.findCharacteristicByKey(usabilityKey);
+ if (usability != null) {
+ if (usability.getOrder() != newUsabilityOrder) {
+ usability.setOrder(newUsabilityOrder);
+ characteristicsContext.updateCharacteristicOrder(usability.getKey(), usability.getOrder());
+ }
+ } else {
+ usability = new Characteristic().setKey(usabilityKey).setName("Usability").setOrder(newUsabilityOrder);
+ characteristicsContext.insertCharacteristic(usability);
+ }
+
+ createSubCharacteristic(characteristicsContext, "USABILITY_ACCESSIBILITY", "Accessibility", usabilityKey);
+ createSubCharacteristic(characteristicsContext, "USABILITY_EASE_OF_USE", "Ease of Use", usabilityKey);
+ createSubCharacteristic(characteristicsContext, USABILITY_KEY + COMPLIANCE_KEY_SUFFIX, "Usability " + COMPLIANCE_NAME, usabilityKey);
+ }
+
+ private void createSubCharacteristic(CharacteristicsContext characteristicsContext,
+ String subCharacteristicKey, String subCharacteristicName, String parentKey) throws SQLException {
+ Characteristic parent = characteristicsContext.findCharacteristicByKey(parentKey);
+ if (parent != null) {
+ Characteristic subCharacteristic = characteristicsContext.findSubCharacteristicByKey(subCharacteristicKey, parent);
+ if (subCharacteristic == null) {
+ characteristicsContext.insertCharacteristic(new Characteristic().setKey(subCharacteristicKey).setName(subCharacteristicName).setParentId(parent.getId()));
+ }
+ }
+ // If the characteristic parent does not exits, the sub-characteristic is not added
+ }
+
+ private static class Characteristic {
+ private Integer id;
+ private String key;
+ private String name;
+ private Integer order;
+ private Integer parentId;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public Characteristic setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Characteristic setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Characteristic setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * On a characteristic, the order can never be null
+ */
+ public Integer getOrder() {
+ return parentId == null && order != null ? order : null;
+ }
+
+ public Characteristic setOrder(@Nullable Integer order) {
+ this.order = order;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getParentId() {
+ return parentId;
+ }
+
+ public Characteristic setParentId(@Nullable Integer parentId) {
+ this.parentId = parentId;
+ return this;
+ }
+ }
+
+ private static class CharacteristicsContext {
+ private final System2 system;
+ Context context;
+ Date now;
+ List<Characteristic> characteristics;
+
+ public CharacteristicsContext(Context context, System2 system) throws SQLException {
+ this.context = context;
+ this.system = system;
+ init();
+ }
+
+ private void init() throws SQLException {
+ now = new Date(system.now());
+ characteristics = selectEnabledCharacteristics();
+ }
+
+ public List<Characteristic> characteristics() {
+ return characteristics;
+ }
+
+ @CheckForNull
+ public Characteristic findCharacteristicByKey(final String key) {
+ Characteristic characteristic = Iterables.find(characteristics, new CharacteristicKey(key), null);
+ if (characteristic != null && characteristic.getParentId() != null) {
+ throw MessageException.of(String.format("'%s' must be a characteristic. " + ERROR_SUFFIX, characteristic.getName()));
+ }
+ return characteristic;
+ }
+
+ @CheckForNull
+ public Characteristic findSubCharacteristicByKey(final String key, Characteristic parent) {
+ Characteristic characteristic = Iterables.find(characteristics, new CharacteristicKey(key), null);
+ if (characteristic != null) {
+ Integer parentId = characteristic.getParentId();
+ if (parentId == null) {
+ throw MessageException.of(String.format("'%s' must be a sub-characteristic. " + ERROR_SUFFIX, characteristic.getName()));
+ } else if (!parentId.equals(parent.getId())) {
+ throw MessageException.of(String.format("'%s' must be defined under '%s'. " + ERROR_SUFFIX, characteristic.getName(), parent.getName()));
+ }
+ }
+ return characteristic;
+ }
+
+ private List<Characteristic> selectEnabledCharacteristics() throws SQLException {
+ return context.prepareSelect(
+ // Exclude requirements (to not fail when coming from a version older than 4.3)
+ "SELECT c.id, c.kee, c.name, c.characteristic_order, c.parent_id FROM characteristics c WHERE c.enabled=? AND c.rule_id IS NULL ORDER BY c.characteristic_order")
+ .setBoolean(1, true)
+ .list(new CharacteristicReader());
+ }
+
+ private int selectCharacteristicId(String key) throws SQLException {
+ Long id = context.prepareSelect(
+ "SELECT c.id FROM characteristics c WHERE c.kee = ? AND c.enabled=?")
+ .setString(1, key)
+ .setBoolean(2, true)
+ .get(Select.LONG_READER);
+ if (id != null) {
+ return id.intValue();
+ } else {
+ throw new IllegalStateException(String.format("Characteristic '%s' could not be inserted", key));
+ }
+ }
+
+ public void insertCharacteristic(Characteristic characteristic) throws SQLException {
+ if (characteristic.getParentId() == null) {
+ LOGGER.info("Insert new characteristic '{}'", characteristic.getKey());
+ } else {
+ LOGGER.info("Insert new sub characteristic '{}'", characteristic.getKey());
+ }
+
+ context.prepareUpsert("INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at) VALUES (?, ?, ?, ?, ?, ?)")
+ .setString(1, characteristic.getKey())
+ .setString(2, characteristic.getName())
+ .setInt(3, characteristic.getParentId())
+ .setInt(4, characteristic.getOrder())
+ .setBoolean(5, true)
+ .setDate(6, now)
+ .execute()
+ .commit();
+ characteristic.setId(selectCharacteristicId(characteristic.getKey()));
+
+ characteristics.add(characteristic);
+ }
+
+ public void updateCharacteristicOrder(String key, Integer order) throws SQLException {
+ LOGGER.info("Update characteristic '{}' order to {}", key, order);
+
+ context.prepareUpsert("UPDATE characteristics SET characteristic_order=?, updated_at=? WHERE kee=?")
+ .setInt(1, order)
+ .setDate(2, now)
+ .setString(3, key)
+ .execute()
+ .commit();
+ }
+
+ private static class CharacteristicReader implements Select.RowReader<Characteristic> {
+ @Override
+ public Characteristic read(Select.Row row) throws SQLException {
+ return new Characteristic()
+ .setId(row.getInt(1))
+ .setKey(row.getString(2))
+ .setName(row.getString(3))
+ .setOrder(row.getNullableInt(4))
+ .setParentId(row.getNullableInt(5));
+ }
+ }
+ }
+
+ private static class CharacteristicKey implements Predicate<Characteristic> {
+ private final String key;
+
+ public CharacteristicKey(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public boolean apply(@Nullable Characteristic input) {
+ return input != null && input.key.equals(key);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/CopyScmAccountsFromAuthorsToUsers.java b/sonar-db/src/main/java/org/sonar/db/version/v51/CopyScmAccountsFromAuthorsToUsers.java
new file mode 100644
index 00000000000..0ae65deb79f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/CopyScmAccountsFromAuthorsToUsers.java
@@ -0,0 +1,172 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.CheckForNull;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.ProgressLogger;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.Upsert;
+import org.sonar.db.version.UpsertImpl;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class CopyScmAccountsFromAuthorsToUsers extends BaseDataChange {
+
+ private static final char SCM_ACCOUNTS_SEPARATOR = '\n';
+
+ private final System2 system;
+ private final AtomicLong counter = new AtomicLong(0L);
+
+ public CopyScmAccountsFromAuthorsToUsers(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(final Context context) throws SQLException {
+ ProgressLogger progress = ProgressLogger.create(getClass(), counter);
+ progress.start();
+ final Long now = system.now();
+
+ try {
+ final Multimap<Long, String> authorsByPersonId = ArrayListMultimap.create();
+ context.prepareSelect("SELECT a.person_id, a.login FROM authors a," +
+ " (SELECT person_id, COUNT(*) AS nb FROM authors GROUP BY person_id HAVING COUNT(*) > 1) group_by_person" +
+ " WHERE a.person_id = group_by_person.person_id "
+ ).scroll(new AuthorsByPersonIdHandler(authorsByPersonId));
+
+ Upsert update = context.prepareUpsert("UPDATE users SET scm_accounts = ?, updated_at = ? WHERE id = ?");
+ for (Long personId : authorsByPersonId.keySet()) {
+ List<String> authors = newArrayList(authorsByPersonId.get(personId));
+ List<User> users = selectUsersFromLoginOrEmail(context, authors);
+ if (users.size() == 1) {
+ User user = users.get(0);
+ if (authors.contains(user.login)) {
+ authors.remove(user.login);
+ }
+ if (authors.contains(user.email)) {
+ authors.remove(user.email);
+ }
+ if (!authors.isEmpty()) {
+ update
+ .setString(1, encodeScmAccounts(authors))
+ .setLong(2, now)
+ .setLong(3, user.id)
+ .addBatch();
+ counter.getAndIncrement();
+ }
+ }
+ }
+ if (((UpsertImpl) update).getBatchCount() > 0L) {
+ update.execute().commit();
+ }
+ update.close();
+
+ progress.log();
+ } finally {
+ progress.stop();
+ }
+ }
+
+ private List<User> selectUsersFromLoginOrEmail(Context context, Collection<String> authors) throws SQLException {
+ final List<User> users = newArrayList();
+ StringBuilder sql = new StringBuilder("SELECT u.id, u.login, u.email, u.scm_accounts FROM users u WHERE u.active=? AND (");
+ for (int i = 0; i < authors.size(); i++) {
+ if (i < authors.size() - 1) {
+ sql.append("u.login=? OR u.email=? OR ");
+ } else {
+ sql.append("u.login=? OR u.email=?)");
+ }
+ }
+ Select select = context.prepareSelect(sql.toString());
+ select.setBoolean(1, true);
+ int currentIndex = 1;
+ for (String author : authors) {
+ currentIndex++;
+ select.setString(currentIndex, author);
+ currentIndex++;
+ select.setString(currentIndex, author);
+ }
+
+ select.scroll(new UsersHandler(users));
+ return users;
+ }
+
+ @CheckForNull
+ private static String encodeScmAccounts(List<String> scmAccounts) {
+ if (scmAccounts.isEmpty()) {
+ return null;
+ }
+ return SCM_ACCOUNTS_SEPARATOR + Joiner.on(SCM_ACCOUNTS_SEPARATOR).join(scmAccounts) + SCM_ACCOUNTS_SEPARATOR;
+ }
+
+ private static class User {
+ Long id;
+ String login;
+ String email;
+ String scmAccounts;
+
+ User(Long id, String login, String email, String scmAccounts) {
+ this.id = id;
+ this.login = login;
+ this.email = email;
+ this.scmAccounts = scmAccounts;
+ }
+ }
+
+ private static class AuthorsByPersonIdHandler implements Select.RowHandler {
+
+ private final Multimap<Long, String> authorsByPersonId;
+
+ private AuthorsByPersonIdHandler(Multimap<Long, String> authorsByPersonId) {
+ this.authorsByPersonId = authorsByPersonId;
+ }
+
+ @Override
+ public void handle(Select.Row row) throws SQLException {
+ authorsByPersonId.put(row.getNullableLong(1), row.getNullableString(2));
+ }
+ }
+
+ private static class UsersHandler implements Select.RowHandler {
+
+ private final List<User> users;
+
+ private UsersHandler(List<User> users) {
+ this.users = users;
+ }
+
+ @Override
+ public void handle(Select.Row row) throws SQLException {
+ users.add(new User(row.getNullableLong(1), row.getNullableString(2), row.getNullableString(3), row.getNullableString(4)));
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/DropIssuesColumns.java b/sonar-db/src/main/java/org/sonar/db/version/v51/DropIssuesColumns.java
new file mode 100644
index 00000000000..a8a0557e6a8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/DropIssuesColumns.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.DdlChange;
+import org.sonar.db.version.DropColumnsBuilder;
+
+/**
+ * Drop the following columns from the issues table :
+ * - issue_creation_date
+ * - issue_update_date
+ * - issue_close_date
+ * - component_id
+ * - root_component_id
+ */
+public class DropIssuesColumns extends DdlChange {
+
+ private final Database db;
+
+ public DropIssuesColumns(Database db) {
+ super(db);
+ this.db = db;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(generateSql());
+ }
+
+ @VisibleForTesting
+ String generateSql() {
+ return new DropColumnsBuilder(db.getDialect(), "issues",
+ "issue_creation_date", "issue_update_date", "issue_close_date", "component_id", "root_component_id")
+ .build();
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedAnalysisReportsLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedAnalysisReportsLongDates.java
new file mode 100644
index 00000000000..42434d8d727
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedAnalysisReportsLongDates.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedAnalysisReportsLongDates extends BaseDataChange {
+
+ private final System2 system;
+
+ public FeedAnalysisReportsLongDates(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final long now = system.now();
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT a.created_at, a.updated_at, a.started_at, a.finished_at, a.id FROM analysis_reports a WHERE created_at_ms IS NULL");
+ massUpdate.update("UPDATE analysis_reports SET created_at_ms=?, updated_at_ms=?, started_at_ms=?, finished_at_ms=? WHERE id=?");
+ massUpdate.rowPluralName("analysis_reports");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Date createdAt = row.getNullableDate(1);
+ Date updatedAt = row.getNullableDate(2);
+ Date startedAt = row.getNullableDate(3);
+ Date finishedAt = row.getNullableDate(4);
+ Long id = row.getNullableLong(5);
+
+ update.setLong(1, createdAt == null ? now : Math.min(now, createdAt.getTime()));
+ update.setLong(2, updatedAt == null ? now : Math.min(now, updatedAt.getTime()));
+ update.setLong(3, startedAt == null ? null : startedAt.getTime());
+ update.setLong(4, finishedAt == null ? null : finishedAt.getTime());
+ update.setLong(5, id);
+ return true;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedEventsLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedEventsLongDates.java
new file mode 100644
index 00000000000..151fa7c9cff
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedEventsLongDates.java
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedEventsLongDates extends BaseDataChange {
+
+ private final System2 system2;
+
+ public FeedEventsLongDates(Database db, System2 system2) {
+ super(db);
+ this.system2 = system2;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate
+ .select("SELECT e.event_date, e.created_at, e.id FROM events e WHERE event_date_ms IS NULL");
+ massUpdate
+ .update("UPDATE events SET event_date_ms=?, created_at_ms=? WHERE id=?");
+ massUpdate.rowPluralName("events");
+ massUpdate.execute(new EventDateHandler(system2.now()));
+ }
+
+ private static class EventDateHandler implements MassUpdate.Handler {
+
+ private final long now;
+
+ public EventDateHandler(long now) {
+ this.now = now;
+ }
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Date eventDate = row.getNullableDate(1);
+ long eventTime = eventDate == null ? now : Math.min(now, eventDate.getTime());
+ update.setLong(1, eventTime);
+ Date createdAt = row.getNullableDate(2);
+ update.setLong(2, createdAt == null ? eventTime : Math.min(now, createdAt.getTime()));
+
+ Long id = row.getNullableLong(3);
+ update.setLong(3, id);
+
+ return true;
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedFileSourcesBinaryData.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedFileSourcesBinaryData.java
new file mode 100644
index 00000000000..7e3d5e5ef74
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedFileSourcesBinaryData.java
@@ -0,0 +1,163 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v51;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import java.sql.SQLException;
+import java.util.Iterator;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.Database;
+import org.sonar.db.source.FileSourceDto;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+import org.sonar.server.source.db.FileSourceDb;
+
+public class FeedFileSourcesBinaryData extends BaseDataChange {
+
+ public FeedFileSourcesBinaryData(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("issues");
+ update.select("SELECT id,data FROM file_sources WHERE binary_data is null");
+ update.update("UPDATE file_sources SET binary_data=? WHERE id=?");
+ update.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long fileSourceId = row.getNullableLong(1);
+ update.setBytes(1, toBinary(fileSourceId, row.getNullableString(2)));
+ update.setLong(2, fileSourceId);
+ return true;
+ }
+ });
+ }
+
+ private byte[] toBinary(Long fileSourceId, @Nullable String data) {
+ FileSourceDb.Data.Builder dataBuilder = FileSourceDb.Data.newBuilder();
+ CSVParser parser = null;
+ try {
+ if (data != null) {
+ parser = CSVParser.parse(data, CSVFormat.DEFAULT);
+ Iterator<CSVRecord> rows = parser.iterator();
+ int line = 1;
+ while (rows.hasNext()) {
+ CSVRecord row = rows.next();
+ if (row.size() == 16) {
+
+ FileSourceDb.Line.Builder lineBuilder = dataBuilder.addLinesBuilder();
+ lineBuilder.setLine(line);
+ String s = row.get(0);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setScmRevision(s);
+ }
+ s = row.get(1);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setScmAuthor(s);
+ }
+ s = row.get(2);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setScmDate(DateUtils.parseDateTimeQuietly(s).getTime());
+ }
+ s = row.get(3);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setUtLineHits(Integer.parseInt(s));
+ }
+ s = row.get(4);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setUtConditions(Integer.parseInt(s));
+ }
+ s = row.get(5);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setUtCoveredConditions(Integer.parseInt(s));
+ }
+ s = row.get(6);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setItLineHits(Integer.parseInt(s));
+ }
+ s = row.get(7);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setItConditions(Integer.parseInt(s));
+ }
+ s = row.get(8);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setItCoveredConditions(Integer.parseInt(s));
+ }
+ s = row.get(9);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setOverallLineHits(Integer.parseInt(s));
+ }
+ s = row.get(10);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setOverallConditions(Integer.parseInt(s));
+ }
+ s = row.get(11);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setOverallCoveredConditions(Integer.parseInt(s));
+ }
+ s = row.get(12);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setHighlighting(s);
+ }
+ s = row.get(13);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.setSymbols(s);
+ }
+ s = row.get(14);
+ if (StringUtils.isNotEmpty(s)) {
+ lineBuilder.addAllDuplication(splitIntegers(s));
+ }
+ s = row.get(15);
+ if (s != null) {
+ lineBuilder.setSource(s);
+ }
+ }
+ line++;
+ }
+ }
+ return FileSourceDto.encodeSourceData(dataBuilder.build());
+ } catch (Exception e) {
+ throw new IllegalStateException("Invalid FILE_SOURCES.DATA on row with ID " + fileSourceId + ": " + data, e);
+ } finally {
+ IOUtils.closeQuietly(parser);
+ }
+ }
+
+ private static Iterable<Integer> splitIntegers(String s) {
+ return Iterables.transform(Splitter.on(',').omitEmptyStrings().trimResults().split(s), new Function<String, Integer>() {
+ @Override
+ public Integer apply(@Nonnull String input) {
+ return Integer.parseInt(input);
+ }
+ });
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueChangesLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueChangesLongDates.java
new file mode 100644
index 00000000000..78c6a81b334
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueChangesLongDates.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedIssueChangesLongDates extends BaseDataChange {
+
+ private final System2 system;
+
+ public FeedIssueChangesLongDates(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final long now = system.now();
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT i.created_at, i.updated_at, i.issue_change_creation_date, i.id FROM issue_changes i WHERE created_at_ms IS NULL");
+ massUpdate.update("UPDATE issue_changes SET created_at_ms=?, updated_at_ms=?, issue_change_creation_date_ms=? WHERE id=?");
+ massUpdate.rowPluralName("issue_changes");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Date createdAt = row.getNullableDate(1);
+ Date updatedAt = row.getNullableDate(2);
+ Date functionalCreatedAt = row.getNullableDate(3);
+ Long id = row.getNullableLong(4);
+
+ update.setLong(1, createdAt == null ? now : Math.min(now, createdAt.getTime()));
+ update.setLong(2, updatedAt == null ? now : Math.min(now, updatedAt.getTime()));
+ update.setLong(3, functionalCreatedAt == null ? null : functionalCreatedAt.getTime());
+ update.setLong(4, id);
+ return true;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueComponentUuids.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueComponentUuids.java
new file mode 100644
index 00000000000..0bfe58afd52
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueComponentUuids.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.MassUpdate.Handler;
+import org.sonar.db.version.Select.Row;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedIssueComponentUuids extends BaseDataChange {
+
+ public FeedIssueComponentUuids(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("issues");
+ update.select(
+ "SELECT c.uuid, c.project_uuid, i.id " +
+ "FROM issues i " +
+ "INNER JOIN projects c ON i.component_id=c.id " +
+ "WHERE i.component_uuid is null");
+ update.update("UPDATE issues SET component_uuid=?, project_uuid=? WHERE id=?");
+ update.execute(new SqlRowHandler());
+ }
+
+ private static class SqlRowHandler implements Handler {
+ @Override
+ public boolean handle(Row row, SqlStatement update) throws SQLException {
+ update.setString(1, row.getNullableString(1));
+ update.setString(2, row.getNullableString(2));
+ update.setLong(3, row.getNullableLong(3));
+
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueTags.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueTags.java
new file mode 100644
index 00000000000..f84dd6b3688
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssueTags.java
@@ -0,0 +1,84 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v51;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import java.sql.SQLException;
+import java.util.Map;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.MassUpdate.Handler;
+import org.sonar.db.version.Select.Row;
+import org.sonar.db.version.Select.RowHandler;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * SONAR-5897
+ */
+public class FeedIssueTags extends BaseDataChange {
+
+ private static final Joiner TAG_JOINER = Joiner.on(',').skipNulls();
+
+ private final long now;
+
+ public FeedIssueTags(Database db, System2 system) {
+ super(db);
+ this.now = system.now();
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+
+ final Map<Integer, String> tagsByRuleId = Maps.newHashMap();
+ context.prepareSelect("SELECT id, system_tags, tags FROM rules").scroll(new RowHandler() {
+ @Override
+ public void handle(Row row) throws SQLException {
+ Integer id = row.getNullableInt(1);
+ tagsByRuleId.put(id, StringUtils.trimToNull(TAG_JOINER.join(
+ StringUtils.trimToNull(row.getNullableString(2)),
+ StringUtils.trimToNull(row.getNullableString(3)))));
+ }
+ });
+
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("issues");
+ update.select("SELECT id, rule_id FROM issues WHERE tags IS NULL");
+ update.update("UPDATE issues SET tags = ?, updated_at = ? WHERE id = ?");
+ update.execute(new Handler() {
+ @Override
+ public boolean handle(Row row, SqlStatement update) throws SQLException {
+ Long id = row.getNullableLong(1);
+ Integer ruleId = row.getNullableInt(2);
+ boolean updated = false;
+ if (tagsByRuleId.get(ruleId) != null) {
+ updated = true;
+ update.setString(1, tagsByRuleId.get(ruleId));
+ update.setLong(2, now);
+ update.setLong(3, id);
+ }
+ return updated;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssuesLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssuesLongDates.java
new file mode 100644
index 00000000000..66ba13b9248
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedIssuesLongDates.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedIssuesLongDates extends BaseDataChange {
+
+ private final System2 system2;
+
+ public FeedIssuesLongDates(Database db, System2 system2) {
+ super(db);
+ this.system2 = system2;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final long now = system2.now();
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate
+ .select("SELECT i.issue_creation_date, i.issue_update_date, i.issue_close_date, i.id FROM issues i WHERE issue_creation_date_ms IS NULL");
+ massUpdate
+ .update("UPDATE issues SET issue_creation_date_ms=?, issue_update_date_ms=?, issue_close_date_ms=? WHERE id=?");
+ massUpdate.rowPluralName("issues");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ for (int i = 1; i <= 3; i++) {
+ Date date = row.getNullableDate(i);
+ update.setLong(i, date == null ? null : Math.min(now, date.getTime()));
+ }
+
+ Long id = row.getNullableLong(4);
+ update.setLong(4, id);
+
+ return true;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedManualMeasuresLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedManualMeasuresLongDates.java
new file mode 100644
index 00000000000..cb250c840d0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedManualMeasuresLongDates.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedManualMeasuresLongDates extends BaseDataChange {
+
+ private final System2 system2;
+
+ public FeedManualMeasuresLongDates(Database db, System2 system2) {
+ super(db);
+ this.system2 = system2;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final long now = system2.now();
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate
+ .select("SELECT m.created_at, m.updated_at, m.id FROM manual_measures m WHERE created_at_ms IS NULL");
+ massUpdate
+ .update("UPDATE manual_measures SET created_at_ms=?, updated_at_ms=? WHERE id=?");
+ massUpdate.rowPluralName("manual measures");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ for (int i = 1; i <= 2; i++) {
+ Date date = row.getNullableDate(i);
+ update.setLong(i, date == null ? null : Math.min(now, date.getTime()));
+ }
+
+ Long id = row.getNullableLong(3);
+ update.setLong(3, id);
+
+ return true;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedSnapshotsLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedSnapshotsLongDates.java
new file mode 100644
index 00000000000..5e5ae4f149b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedSnapshotsLongDates.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedSnapshotsLongDates extends BaseDataChange {
+
+ private final System2 system2;
+
+ public FeedSnapshotsLongDates(Database db, System2 system2) {
+ super(db);
+ this.system2 = system2;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final long now = system2.now();
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate
+ .select("SELECT s.created_at, s.build_date, s.period1_date, s.period2_date, s.period3_date, s.period4_date, s.period5_date, s.id FROM snapshots s WHERE created_at_ms IS NULL");
+ massUpdate
+ .update("UPDATE snapshots SET created_at_ms=?, build_date_ms=?, period1_date_ms=?, period2_date_ms=?, period3_date_ms=?, period4_date_ms=?, period5_date_ms=? WHERE id=?");
+ massUpdate.rowPluralName("snapshots");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ for (int i = 1; i <= 7; i++) {
+ Date date = row.getNullableDate(i);
+ update.setLong(i, date == null ? null : Math.min(now, date.getTime()));
+ }
+
+ Long id = row.getNullableLong(8);
+ update.setLong(8, id);
+
+ return true;
+ }
+ });
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/FeedUsersLongDates.java b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedUsersLongDates.java
new file mode 100644
index 00000000000..0196e62fc57
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/FeedUsersLongDates.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedUsersLongDates extends BaseDataChange {
+
+ private final System2 system;
+
+ public FeedUsersLongDates(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT u.id, u.created_at, u.updated_at FROM users u WHERE created_at_ms IS NULL");
+ massUpdate.update("UPDATE users SET created_at_ms=?, updated_at_ms=? WHERE id=?");
+ massUpdate.rowPluralName("users");
+ massUpdate.execute(new RowHandler(system.now()));
+ }
+
+ private static class RowHandler implements MassUpdate.Handler {
+
+ private final long now;
+
+ private RowHandler(long now) {
+ this.now = now;
+ }
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long id = row.getNullableLong(1);
+ Date createdAt = row.getNullableDate(2);
+ Date updatedAt = row.getNullableDate(3);
+
+ if (createdAt == null) {
+ update.setLong(1, now);
+ } else {
+ update.setLong(1, Math.min(now, createdAt.getTime()));
+ }
+ if (updatedAt == null) {
+ update.setLong(2, now);
+ } else {
+ update.setLong(2, Math.min(now, updatedAt.getTime()));
+ }
+ update.setLong(3, id);
+ return true;
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/RemovePermissionsOnModulesMigrationStep.java b/sonar-db/src/main/java/org/sonar/db/version/v51/RemovePermissionsOnModulesMigrationStep.java
new file mode 100644
index 00000000000..bb70c544e89
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/RemovePermissionsOnModulesMigrationStep.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * See http://jira.sonarsource.com/browse/SONAR-5596
+ *
+ * It's no possible to set permission on a module or a sub-view, but the batch was setting default permission on it on their creation.
+ * As now it's no more the case, we need to purge this useless data.
+ *
+ * @since 5.1
+ */
+public class RemovePermissionsOnModulesMigrationStep extends BaseDataChange {
+
+ public RemovePermissionsOnModulesMigrationStep(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ removeUserRolePermissions(context, "user_roles", "user roles");
+ removeUserRolePermissions(context, "group_roles", "group roles");
+ }
+
+ private void removeUserRolePermissions(Context context, String tableName, String pluralName) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT r.id " +
+ "FROM " + tableName + " r " +
+ " INNER JOIN projects ON projects.id = r.resource_id " +
+ "WHERE projects.module_uuid IS NOT NULL");
+ massUpdate.update("DELETE FROM " + tableName + " WHERE id=?");
+ massUpdate.rowPluralName(pluralName);
+ massUpdate.execute(MigrationHandler.INSTANCE);
+ }
+
+ private enum MigrationHandler implements MassUpdate.Handler {
+ INSTANCE;
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ update.setLong(1, row.getLong(1));
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/RenameComponentRelatedParamsInIssueFilters.java b/sonar-db/src/main/java/org/sonar/db/version/v51/RenameComponentRelatedParamsInIssueFilters.java
new file mode 100644
index 00000000000..73e8e8b9a79
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/RenameComponentRelatedParamsInIssueFilters.java
@@ -0,0 +1,89 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v51;
+
+import com.google.common.collect.Lists;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class RenameComponentRelatedParamsInIssueFilters extends BaseDataChange {
+
+ private static final char FIELD_SEPARATOR = '|';
+ private static final String LIKE_PREFIX = "data like '%";
+ private static final String LIKE_SUFFIX = "%' or ";
+ private static final String COMPONENT_UUIDS = "componentUuids=";
+ private static final String COMPONENT_ROOT_UUIDS = "componentRootUuids=";
+
+ private final System2 system;
+
+ public RenameComponentRelatedParamsInIssueFilters(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ final Date now = new Date(system.now());
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("select id,data from issue_filters where " +
+ LIKE_PREFIX + COMPONENT_UUIDS + LIKE_SUFFIX +
+ LIKE_PREFIX + COMPONENT_ROOT_UUIDS + "%'");
+ massUpdate.update("update issue_filters set data=?, updated_at=? where id=?");
+ massUpdate.rowPluralName("issue filters");
+ massUpdate.execute(new RenameComponentRelatedParamsHandler(now));
+ }
+
+ private static final class RenameComponentRelatedParamsHandler implements MassUpdate.Handler {
+ private final Date now;
+
+ private RenameComponentRelatedParamsHandler(Date now) {
+ this.now = now;
+ }
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ String data = row.getNullableString(2);
+ String[] fields = StringUtils.split(data, FIELD_SEPARATOR);
+
+ List<String> fieldsToKeep = Lists.newArrayList();
+ for (String field : fields) {
+ if (field.startsWith(COMPONENT_UUIDS) || field.startsWith(COMPONENT_ROOT_UUIDS)) {
+ fieldsToKeep.add(
+ field.replace(COMPONENT_UUIDS, "fileUuids=")
+ .replace(COMPONENT_ROOT_UUIDS, "moduleUuids="));
+ } else {
+ fieldsToKeep.add(field);
+ }
+ }
+ update.setString(1, StringUtils.join(fieldsToKeep, FIELD_SEPARATOR));
+ update.setDate(2, now);
+ update.setLong(3, row.getNullableLong(1));
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v51/UpdateProjectsModuleUuidPath.java b/sonar-db/src/main/java/org/sonar/db/version/v51/UpdateProjectsModuleUuidPath.java
new file mode 100644
index 00000000000..d07f9acc2f8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v51/UpdateProjectsModuleUuidPath.java
@@ -0,0 +1,113 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v51;
+
+import java.sql.SQLException;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.MassUpdate.Handler;
+import org.sonar.db.version.Select.Row;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * SONAR-6054
+ * SONAR-6119
+ */
+public class UpdateProjectsModuleUuidPath extends BaseDataChange {
+
+ private static final String SEP = ".";
+
+ public UpdateProjectsModuleUuidPath(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("components");
+ update.select("SELECT p.id, p.module_uuid_path, p.uuid, p.scope, p.qualifier FROM projects p");
+ update.update("UPDATE projects SET module_uuid_path=? WHERE id=?");
+ update.execute(new ModuleUuidPathUpdateHandler());
+ }
+
+ private static final class ModuleUuidPathUpdateHandler implements Handler {
+ @Override
+ public boolean handle(Row row, SqlStatement update) throws SQLException {
+ Long id = row.getNullableLong(1);
+ String moduleUuidPath = row.getNullableString(2);
+ String uuid = row.getNullableString(3);
+ String scope = row.getNullableString(4);
+ String qualifier = row.getNullableString(5);
+
+ boolean needUpdate = false;
+ String newModuleUuidPath = moduleUuidPath;
+
+ if (needUpdateForEmptyPath(newModuleUuidPath)) {
+ newModuleUuidPath = SEP + uuid + SEP;
+ needUpdate = true;
+ }
+
+ if (needUpdateForSeparators(newModuleUuidPath)) {
+ newModuleUuidPath = newModuleUuidPathWithSeparators(newModuleUuidPath);
+ needUpdate = true;
+ }
+
+ if (needUpdateToIncludeItself(newModuleUuidPath, uuid, scope, qualifier)) {
+ newModuleUuidPath = newModuleUuidPathIncludingItself(newModuleUuidPath, uuid);
+ needUpdate = true;
+ }
+
+ if (needUpdate) {
+ update.setString(1, newModuleUuidPath);
+ update.setLong(2, id);
+ }
+ return needUpdate;
+ }
+
+ private static boolean needUpdateForEmptyPath(@Nullable String moduleUuidPath) {
+ return StringUtils.isEmpty(moduleUuidPath) || SEP.equals(moduleUuidPath);
+ }
+
+ private static boolean needUpdateForSeparators(String moduleUuidPath) {
+ return !(moduleUuidPath.startsWith(SEP) && moduleUuidPath.endsWith(SEP));
+ }
+
+ private static String newModuleUuidPathWithSeparators(String oldModuleUuidPath) {
+ StringBuilder newModuleUuidPath = new StringBuilder(oldModuleUuidPath);
+ newModuleUuidPath.insert(0, SEP);
+ newModuleUuidPath.append(SEP);
+ return newModuleUuidPath.toString();
+ }
+
+ private static boolean needUpdateToIncludeItself(String moduleUuidPath, @Nullable String uuid, @Nullable String scope, @Nullable String qualifier) {
+ return "PRJ".equals(scope) && !("DEV_PRJ".equals(qualifier)) && !(moduleUuidPath.contains(uuid));
+ }
+
+ private static String newModuleUuidPathIncludingItself(String moduleUuidPath, String uuid) {
+ StringBuilder newModuleUuidPath = new StringBuilder(moduleUuidPath);
+ newModuleUuidPath.append(uuid);
+ newModuleUuidPath.append(SEP);
+ return newModuleUuidPath.toString();
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/AddManualMeasuresComponentUuidColumn.java b/sonar-db/src/main/java/org/sonar/db/version/v52/AddManualMeasuresComponentUuidColumn.java
new file mode 100644
index 00000000000..686db41ae2f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/AddManualMeasuresComponentUuidColumn.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+import org.sonar.db.version.DdlChange;
+
+import static org.sonar.db.version.AddColumnsBuilder.ColumnDef.Type.STRING;
+
+/**
+ * Add the following column to the manual_measures table :
+ * - component_uuid
+ */
+public class AddManualMeasuresComponentUuidColumn extends DdlChange {
+ private final Database db;
+
+ public AddManualMeasuresComponentUuidColumn(Database db) {
+ super(db);
+ this.db = db;
+ }
+
+ @Override
+ public void execute(DdlChange.Context context) throws SQLException {
+ context.execute(generateSql());
+ }
+
+ private String generateSql() {
+ return new AddColumnsBuilder(db.getDialect(), "manual_measures")
+ .addColumn(
+ new AddColumnsBuilder.ColumnDef()
+ .setName("component_uuid")
+ .setType(STRING)
+ .setLimit(50)
+ .setNullable(true)
+ )
+ .build();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/FeedEventsComponentUuid.java b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedEventsComponentUuid.java
new file mode 100644
index 00000000000..a6139e1e946
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedEventsComponentUuid.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedEventsComponentUuid extends BaseDataChange {
+
+ public FeedEventsComponentUuid(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("events");
+ update.select(
+ "SELECT p.uuid, event.id " +
+ "FROM events event " +
+ "INNER JOIN projects p ON p.id=event.resource_id " +
+ "WHERE event.component_uuid is null");
+ update.update("UPDATE events SET component_uuid=? WHERE id=?");
+ update.execute(MigrationHandler.INSTANCE);
+ }
+
+ private enum MigrationHandler implements MassUpdate.Handler {
+ INSTANCE;
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ update.setString(1, row.getString(1));
+ update.setLong(2, row.getLong(2));
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/FeedFileSourcesDataType.java b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedFileSourcesDataType.java
new file mode 100644
index 00000000000..b37e60054b3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedFileSourcesDataType.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+
+public class FeedFileSourcesDataType extends BaseDataChange {
+
+ public FeedFileSourcesDataType(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.prepareUpsert("update file_sources set data_type = 'SOURCE'").execute().commit();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/FeedManualMeasuresComponentUuid.java b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedManualMeasuresComponentUuid.java
new file mode 100644
index 00000000000..36b14ac2e83
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedManualMeasuresComponentUuid.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedManualMeasuresComponentUuid extends BaseDataChange {
+
+ public FeedManualMeasuresComponentUuid(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("manual measures");
+ update.select(
+ "SELECT p.uuid, mm.resource_id " +
+ "FROM manual_measures mm " +
+ "INNER JOIN projects p ON mm.resource_id = p.id " +
+ "WHERE mm.component_uuid IS NULL");
+ update.update("UPDATE manual_measures SET component_uuid=? WHERE resource_id=?");
+ update.execute(new SqlRowHandler());
+ }
+
+ private static class SqlRowHandler implements MassUpdate.Handler {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ update.setString(1, row.getString(1));
+ update.setLong(2, row.getLong(2));
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/FeedMetricsBooleans.java b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedMetricsBooleans.java
new file mode 100644
index 00000000000..f28bc096e5b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedMetricsBooleans.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+
+public class FeedMetricsBooleans extends BaseDataChange {
+
+ public FeedMetricsBooleans(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.prepareUpsert("update metrics set optimized_best_value=?, hidden=?, delete_historical_data=? " +
+ "where user_managed=? or optimized_best_value is null or hidden is null or delete_historical_data is null")
+ .setBoolean(1, false)
+ .setBoolean(2, false)
+ .setBoolean(3, false)
+ .setBoolean(4, true)
+ .execute().commit();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/FeedProjectLinksComponentUuid.java b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedProjectLinksComponentUuid.java
new file mode 100644
index 00000000000..4162aacb2ca
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/FeedProjectLinksComponentUuid.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select.Row;
+import org.sonar.db.version.SqlStatement;
+
+public class FeedProjectLinksComponentUuid extends BaseDataChange {
+
+ public FeedProjectLinksComponentUuid(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("project links");
+ update.select(
+ "SELECT p.uuid, link.id " +
+ "FROM project_links link " +
+ "INNER JOIN projects p ON p.id=link.project_id " +
+ "WHERE link.component_uuid is null");
+ update.update("UPDATE project_links SET component_uuid=? WHERE id=?");
+ update.execute(MigrationHandler.INSTANCE);
+ }
+
+ private enum MigrationHandler implements MassUpdate.Handler {
+ INSTANCE;
+
+ @Override
+ public boolean handle(Row row, SqlStatement update) throws SQLException {
+ update.setString(1, row.getString(1));
+ update.setLong(2, row.getLong(2));
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/MoveProjectProfileAssociation.java b/sonar-db/src/main/java/org/sonar/db/version/v52/MoveProjectProfileAssociation.java
new file mode 100644
index 00000000000..d533aa73ef4
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/MoveProjectProfileAssociation.java
@@ -0,0 +1,136 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v52;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import java.sql.SQLException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.MassUpdate.Handler;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.Select.Row;
+import org.sonar.db.version.Select.RowReader;
+import org.sonar.db.version.SqlStatement;
+import org.sonar.db.version.Upsert;
+
+/**
+ * SonarQube 5.2
+ * SONAR-6328
+ *
+ */
+public class MoveProjectProfileAssociation extends BaseDataChange {
+
+ private static final class ProjectProfileAssociationHandler implements Handler {
+ private final Upsert setDefaultProfile;
+ private final Upsert associateProjectToProfile;
+ private final Table<String, String, String> profileKeysByLanguageThenName;
+
+ private ProjectProfileAssociationHandler(Upsert setDefaultProfile, Upsert associateProjectToProfile, Table<String, String, String> profileKeysByLanguageThenName) {
+ this.setDefaultProfile = setDefaultProfile;
+ this.associateProjectToProfile = associateProjectToProfile;
+ this.profileKeysByLanguageThenName = profileKeysByLanguageThenName;
+ }
+
+ @Override
+ public boolean handle(Row row, SqlStatement update) throws SQLException {
+ Long id = row.getLong(1);
+ String profileLanguage = extractLanguage(row.getString(2));
+ String profileName = row.getString(3);
+ Long projectId = row.getNullableLong(4);
+ String projectUuid = row.getString(5);
+
+ if (profileKeysByLanguageThenName.contains(profileLanguage, profileName)) {
+ String profileKey = profileKeysByLanguageThenName.get(profileLanguage, profileName);
+
+ if (projectUuid == null) {
+ if (projectId == null) {
+ setDefaultProfile.setBoolean(1, true).setString(2, profileKey).execute();
+ } else {
+ LOGGER.warn(String.format("Profile with language '%s' and name '%s' is associated with unknown project '%d', ignored", profileLanguage, profileName, projectId));
+ }
+ } else {
+ associateProjectToProfile.setString(1, projectUuid).setString(2, profileKey).execute();
+ }
+ } else {
+ LOGGER.warn(String.format("Unable to find profile with language '%s' and name '%s', ignored", profileLanguage, profileName));
+ }
+
+ update.setLong(1, id);
+ return true;
+ }
+ }
+
+ private static final Logger LOGGER = Loggers.get(MoveProjectProfileAssociation.class);
+
+ public MoveProjectProfileAssociation(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+
+ final Table<String, String, String> profileKeysByLanguageThenName = getProfileKeysByLanguageThenName(context);
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT prop.id, prop.prop_key, prop.text_value, prop.resource_id, proj.uuid " +
+ "FROM properties prop " +
+ "LEFT OUTER JOIN projects proj ON prop.resource_id = proj.id " +
+ "WHERE prop.prop_key LIKE 'sonar.profile.%'"
+ );
+ massUpdate.update("DELETE FROM properties WHERE id = ?");
+
+ final Upsert setDefaultProfile = context.prepareUpsert("UPDATE rules_profiles SET is_default = ? WHERE kee = ?");
+ final Upsert associateProjectToProfile = context.prepareUpsert("INSERT INTO project_qprofiles (project_uuid, profile_key) VALUES (?, ?)");
+
+ try {
+ massUpdate.execute(new ProjectProfileAssociationHandler(setDefaultProfile, associateProjectToProfile, profileKeysByLanguageThenName));
+ } finally {
+ associateProjectToProfile.close();
+ setDefaultProfile.close();
+ }
+ }
+
+ private static String extractLanguage(String propertyKey) {
+ return propertyKey.substring("sonar.profile.".length());
+ }
+
+ private Table<String, String, String> getProfileKeysByLanguageThenName(final Context context) throws SQLException {
+ final Table<String, String, String> profilesByLanguageAndName = HashBasedTable.create();
+
+ Select selectProfiles = context.prepareSelect("SELECT kee, name, language FROM rules_profiles");
+ try {
+ selectProfiles.list(new RowReader<Void>() {
+ @Override
+ public Void read(Row row) throws SQLException {
+ profilesByLanguageAndName.put(row.getString(3), row.getString(2), row.getString(1));
+ return null;
+ }
+ });
+ } finally {
+ selectProfiles.close();
+ }
+
+ return profilesByLanguageAndName;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveComponentLibraries.java b/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveComponentLibraries.java
new file mode 100644
index 00000000000..11945b60316
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveComponentLibraries.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * Remove all components having qualifier 'LIB'
+ */
+public class RemoveComponentLibraries extends BaseDataChange {
+
+ public RemoveComponentLibraries(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("component libraries");
+ update.select("SELECT p.id FROM projects p WHERE p.qualifier='LIB'");
+ update.update("DELETE FROM projects WHERE id=?");
+ update.execute(MigrationHandler.INSTANCE);
+ }
+
+ private enum MigrationHandler implements MassUpdate.Handler {
+ INSTANCE;
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ update.setLong(1, row.getLong(1));
+ return true;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveDuplicatedComponentKeys.java b/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveDuplicatedComponentKeys.java
new file mode 100644
index 00000000000..557c562fca0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveDuplicatedComponentKeys.java
@@ -0,0 +1,163 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.Nonnull;
+import org.sonar.core.util.ProgressLogger;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.Upsert;
+
+/**
+ * Remove all duplicated component that have the same keys.
+ * For each duplicated component key :
+ * <ul>
+ * <li>Only the enabled one or last one (with the latest id) is kept</li>
+ * <li>When deleting a component, all its issues are linked to the remaining component</li>
+ * </ul>
+ */
+public class RemoveDuplicatedComponentKeys extends BaseDataChange {
+
+ private final AtomicLong counter = new AtomicLong(0L);
+
+ public RemoveDuplicatedComponentKeys(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(final Context context) throws SQLException {
+ Upsert componentUpdate = context.prepareUpsert("DELETE FROM projects WHERE id=?");
+ Upsert issuesUpdate = context.prepareUpsert("UPDATE issues SET component_uuid=?, project_uuid=? WHERE component_uuid=?");
+
+ ProgressLogger progress = ProgressLogger.create(getClass(), counter);
+ progress.start();
+ try {
+ RemoveDuplicatedComponentHandler handler = new RemoveDuplicatedComponentHandler(context, componentUpdate, issuesUpdate);
+ context.prepareSelect(
+ "SELECT p.kee, COUNT(p.kee) FROM projects p " +
+ "GROUP BY p.kee " +
+ "HAVING COUNT(p.kee) > 1")
+ .scroll(handler);
+ if (!handler.isEmpty) {
+ componentUpdate.execute().commit();
+ issuesUpdate.execute().commit();
+ }
+ progress.log();
+ } finally {
+ progress.stop();
+ componentUpdate.close();
+ issuesUpdate.close();
+ }
+ }
+
+ private class RemoveDuplicatedComponentHandler implements Select.RowHandler {
+ private final Context context;
+ private final Upsert componentUpdate;
+ private final Upsert issuesUpdate;
+
+ private boolean isEmpty = true;
+
+ public RemoveDuplicatedComponentHandler(Context context, Upsert componentUpdate, Upsert issuesUpdate) {
+ this.context = context;
+ this.componentUpdate = componentUpdate;
+ this.issuesUpdate = issuesUpdate;
+ }
+
+ @Override
+ public void handle(Select.Row row) throws SQLException {
+ List<Component> components = context
+ .prepareSelect("SELECT p.id, p.uuid, p.project_uuid, p.enabled FROM projects p WHERE p.kee=? ORDER BY id")
+ .setString(1, row.getString(1))
+ .list(ComponentRowReader.INSTANCE);
+ // We keep the enabled component or the last component of the list
+ Component refComponent = FluentIterable.from(components).firstMatch(EnabledComponent.INSTANCE).or(components.get(components.size() - 1));
+ for (Component componentToRemove : FluentIterable.from(components).filter(Predicates.not(new MatchComponentId(refComponent.id)))) {
+ componentUpdate
+ .setLong(1, componentToRemove.id)
+ .addBatch();
+ issuesUpdate
+ .setString(1, refComponent.uuid)
+ .setString(2, refComponent.projectUuid)
+ .setString(3, componentToRemove.uuid)
+ .addBatch();
+ counter.getAndIncrement();
+ isEmpty = false;
+ }
+ }
+
+ public boolean isEmpty() {
+ return isEmpty;
+ }
+ }
+
+ private enum EnabledComponent implements Predicate<Component> {
+ INSTANCE;
+
+ @Override
+ public boolean apply(@Nonnull Component input) {
+ return input.enabled;
+ }
+ }
+
+ private static class MatchComponentId implements Predicate<Component> {
+
+ private final long id;
+
+ public MatchComponentId(long id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean apply(@Nonnull Component input) {
+ return input.id == this.id;
+ }
+ }
+
+ private enum ComponentRowReader implements Select.RowReader<Component> {
+ INSTANCE;
+
+ @Override
+ public Component read(Select.Row row) throws SQLException {
+ return new Component(row.getLong(1), row.getString(2), row.getString(3), row.getBoolean(4));
+ }
+ }
+
+ private static class Component {
+ private final long id;
+ private final String uuid;
+ private final String projectUuid;
+ private final boolean enabled;
+
+ public Component(long id, String uuid, String projectUuid, boolean enabled) {
+ this.id = id;
+ this.uuid = uuid;
+ this.projectUuid = projectUuid;
+ this.enabled = enabled;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveSnapshotLibraries.java b/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveSnapshotLibraries.java
new file mode 100644
index 00000000000..108200dac7e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v52/RemoveSnapshotLibraries.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v52;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+/**
+ * Remove all components having qualifier 'LIB'
+ */
+public class RemoveSnapshotLibraries extends BaseDataChange {
+
+ public RemoveSnapshotLibraries(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate update = context.prepareMassUpdate().rowPluralName("snapshot libraries");
+ update.select("SELECT s.id FROM snapshots s WHERE s.qualifier='LIB'");
+ update.update("DELETE FROM snapshots WHERE id=?");
+ update.execute(MigrationHandler.INSTANCE);
+ }
+
+ private enum MigrationHandler implements MassUpdate.Handler {
+ INSTANCE;
+
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ update.setLong(1, row.getLong(1));
+ return true;
+ }
+ }
+}