]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23213 'api/system/prepare_migration' endpoint
authorClaire Villard <claire.villard@sonarsource.com>
Mon, 30 Sep 2024 13:10:25 +0000 (15:10 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 14 Oct 2024 20:03:02 +0000 (20:03 +0000)
23 files changed:
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AbstractAddMeasuresMigratedColumnToTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AbstractCreateIndexOnMeasuresMigrated.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigrated.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigrated.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateMeasuresTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/README.md [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigratedTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigratedTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateMeasuresTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTableTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTableTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigratedTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigratedTest/schema.sql [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/PrepareMigrationAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SystemWsModule.java
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/platform/ws/example-prepare_migration.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/PrepareMigrationActionTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SystemWsModuleTest.java

diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AbstractAddMeasuresMigratedColumnToTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AbstractAddMeasuresMigratedColumnToTable.java
new file mode 100644 (file)
index 0000000..957b06c
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.def.BooleanColumnDef;
+import org.sonar.server.platform.db.migration.def.ColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AbstractAddMeasuresMigratedColumnToTable extends DdlChange {
+
+  public static final String MIGRATION_FLAG_COLUMN_NAME = "measures_migrated";
+  private final String tableName;
+
+  public AbstractAddMeasuresMigratedColumnToTable(Database db, String tableName) {
+    super(db);
+    this.tableName = tableName;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      if (!DatabaseUtils.tableColumnExists(connection, tableName, MIGRATION_FLAG_COLUMN_NAME)) {
+        ColumnDef columnDef = BooleanColumnDef.newBooleanColumnDefBuilder()
+          .setColumnName(MIGRATION_FLAG_COLUMN_NAME)
+          .setIsNullable(false)
+          .setDefaultValue(false)
+          .build();
+        context.execute(new AddColumnsBuilder(getDialect(), tableName)
+          .addColumn(columnDef)
+          .build());
+      }
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AbstractCreateIndexOnMeasuresMigrated.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AbstractCreateIndexOnMeasuresMigrated.java
new file mode 100644 (file)
index 0000000..8a1f982
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AbstractCreateIndexOnMeasuresMigrated extends DdlChange {
+
+  static final String COLUMN_NAME = "measures_migrated";
+  private final String tableName;
+  private final String indexName;
+
+  public AbstractCreateIndexOnMeasuresMigrated(Database db, String tableName, String indexName) {
+    super(db);
+    this.tableName = tableName;
+    this.indexName = indexName;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      if (!DatabaseUtils.indexExistsIgnoreCase(tableName, indexName, connection)) {
+        context.execute(new CreateIndexBuilder()
+          .setTable(tableName)
+          .setName(indexName)
+          .addColumn(COLUMN_NAME)
+          .build());
+      }
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTable.java
new file mode 100644 (file)
index 0000000..6d45843
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import org.sonar.db.Database;
+
+public class AddMeasuresMigratedColumnToPortfoliosTable extends AbstractAddMeasuresMigratedColumnToTable {
+
+  static final String PORTFOLIOS_TABLE_NAME = "portfolios";
+
+  public AddMeasuresMigratedColumnToPortfoliosTable(Database db) {
+    super(db, PORTFOLIOS_TABLE_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTable.java
new file mode 100644 (file)
index 0000000..6f1475d
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import org.sonar.db.Database;
+
+public class AddMeasuresMigratedColumnToProjectBranchesTable extends AbstractAddMeasuresMigratedColumnToTable {
+
+  public static final String PROJECT_BRANCHES_TABLE_NAME = "project_branches";
+
+  public AddMeasuresMigratedColumnToProjectBranchesTable(Database db) {
+    super(db, PROJECT_BRANCHES_TABLE_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigrated.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigrated.java
new file mode 100644 (file)
index 0000000..414d305
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import org.sonar.db.Database;
+
+public class CreateIndexOnPortfoliosMeasuresMigrated extends AbstractCreateIndexOnMeasuresMigrated {
+
+  static final String TABLE_NAME = "portfolios";
+  static final String INDEX_NAME = "portfolios_measures_migrated";
+
+  public CreateIndexOnPortfoliosMeasuresMigrated(Database db) {
+    super(db, TABLE_NAME, INDEX_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigrated.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigrated.java
new file mode 100644 (file)
index 0000000..2a8e051
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import org.sonar.db.Database;
+
+public class CreateIndexOnProjectBranchesMeasuresMigrated extends AbstractCreateIndexOnMeasuresMigrated {
+
+  static final String TABLE_NAME = "project_branches";
+  static final String INDEX_NAME = "pb_measures_migrated";
+
+  public CreateIndexOnProjectBranchesMeasuresMigrated(Database db) {
+    super(db, TABLE_NAME, INDEX_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateMeasuresTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/CreateMeasuresTable.java
new file mode 100644 (file)
index 0000000..d6a0275
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.CreateTableBuilder;
+import org.sonar.server.platform.db.migration.step.CreateTableChange;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.ClobColumnDef.newClobColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class CreateMeasuresTable extends CreateTableChange {
+  static final String MEASURES_TABLE_NAME = "measures";
+  static final String COLUMN_COMPONENT_UUID = "component_uuid";
+  static final String COLUMN_BRANCH_UUID = "branch_uuid";
+  static final String COLUMN_JSON_VALUE = "json_value";
+  static final String COLUMN_JSON_VALUE_HASH = "json_value_hash";
+  static final String COLUMN_CREATED_AT = "created_at";
+  static final String COLUMN_UPDATED_AT = "updated_at";
+
+  public CreateMeasuresTable(Database db) {
+    super(db, MEASURES_TABLE_NAME);
+  }
+
+  @Override
+  public void execute(DdlChange.Context context, String tableName) throws SQLException {
+    context.execute(new CreateTableBuilder(getDialect(), tableName)
+      .addColumn(newVarcharColumnDefBuilder().setColumnName(COLUMN_COMPONENT_UUID).setIsNullable(false).setLimit(UUID_SIZE).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName(COLUMN_BRANCH_UUID).setIsNullable(false).setLimit(UUID_SIZE).build())
+      .addColumn(newClobColumnDefBuilder().setColumnName(COLUMN_JSON_VALUE).setIsNullable(false).build())
+      .addColumn(newBigIntegerColumnDefBuilder().setColumnName(COLUMN_JSON_VALUE_HASH).setIsNullable(false).build())
+      .addColumn(newBigIntegerColumnDefBuilder().setColumnName(COLUMN_CREATED_AT).setIsNullable(false).build())
+      .addColumn(newBigIntegerColumnDefBuilder().setColumnName(COLUMN_UPDATED_AT).setIsNullable(false).build())
+      .build());
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/README.md b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/README.md
new file mode 100644 (file)
index 0000000..1211491
--- /dev/null
@@ -0,0 +1,2 @@
+Those migrations are not meant to be executed during automatic or manual migrations. 
+They are meant to be executed on a specific action during the normal runtime of the server, for example on a call of some API endpoint.
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/adhoc/package-info.java
new file mode 100644 (file)
index 0000000..0358805
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.platform.db.migration.adhoc;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTableTest.java
new file mode 100644 (file)
index 0000000..e845654
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToPortfoliosTable.MIGRATION_FLAG_COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToPortfoliosTable.PORTFOLIOS_TABLE_NAME;
+
+public class AddMeasuresMigratedColumnToPortfoliosTableTest {
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddMeasuresMigratedColumnToPortfoliosTableTest.class, "schema.sql");
+  private final AddMeasuresMigratedColumnToPortfoliosTable underTest = new AddMeasuresMigratedColumnToPortfoliosTable(db.database());
+
+  @Test
+  public void execute_whenColumnDoesNotExist_shouldCreateColumn() throws SQLException {
+    db.assertColumnDoesNotExist(PORTFOLIOS_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME);
+    underTest.execute();
+    db.assertColumnDefinition(PORTFOLIOS_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME, Types.BOOLEAN, null, false);
+  }
+
+  @Test
+  public void execute_whenColumnAlreadyExists_shouldNotFail() throws SQLException {
+    underTest.execute();
+    assertThatCode(underTest::execute).doesNotThrowAnyException();
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTableTest.java
new file mode 100644 (file)
index 0000000..4aa369b
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToProjectBranchesTable.MIGRATION_FLAG_COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToProjectBranchesTable.PROJECT_BRANCHES_TABLE_NAME;
+
+public class AddMeasuresMigratedColumnToProjectBranchesTableTest {
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddMeasuresMigratedColumnToProjectBranchesTableTest.class, "schema.sql");
+  private final AddMeasuresMigratedColumnToProjectBranchesTable underTest = new AddMeasuresMigratedColumnToProjectBranchesTable(db.database());
+
+  @Test
+  public void execute_whenColumnDoesNotExist_shouldCreateColumn() throws SQLException {
+    db.assertColumnDoesNotExist(PROJECT_BRANCHES_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME);
+    underTest.execute();
+    db.assertColumnDefinition(PROJECT_BRANCHES_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME, Types.BOOLEAN, null, false);
+  }
+
+  @Test
+  public void execute_whenColumnAlreadyExists_shouldNotFail() throws SQLException {
+    underTest.execute();
+    assertThatCode(underTest::execute).doesNotThrowAnyException();
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigratedTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigratedTest.java
new file mode 100644 (file)
index 0000000..ec6cec5
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static org.sonar.server.platform.db.migration.adhoc.CreateIndexOnPortfoliosMeasuresMigrated.COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.adhoc.CreateIndexOnPortfoliosMeasuresMigrated.TABLE_NAME;
+
+public class CreateIndexOnPortfoliosMeasuresMigratedTest {
+
+  private static final String INDEX_NAME = "portfolios_measures_migrated";
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(CreateIndexOnPortfoliosMeasuresMigratedTest.class, "schema.sql");
+  private final CreateIndexOnPortfoliosMeasuresMigrated underTest = new CreateIndexOnPortfoliosMeasuresMigrated(db.database());
+
+  @Test
+  public void migration_should_create_index() throws SQLException {
+    db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+
+    underTest.execute();
+
+    db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    underTest.execute();
+    underTest.execute();
+
+    db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigratedTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigratedTest.java
new file mode 100644 (file)
index 0000000..1618a97
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static org.sonar.server.platform.db.migration.adhoc.CreateIndexOnProjectBranchesMeasuresMigrated.COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.adhoc.CreateIndexOnProjectBranchesMeasuresMigrated.TABLE_NAME;
+
+public class CreateIndexOnProjectBranchesMeasuresMigratedTest {
+
+  private static final String INDEX_NAME = "pb_measures_migrated";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(CreateIndexOnProjectBranchesMeasuresMigratedTest.class, "schema.sql");
+  private final CreateIndexOnProjectBranchesMeasuresMigrated underTest = new CreateIndexOnProjectBranchesMeasuresMigrated(db.database());
+
+  @Test
+  public void migration_should_create_index() throws SQLException {
+    db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+
+    underTest.execute();
+
+    db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    underTest.execute();
+    underTest.execute();
+
+    db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateMeasuresTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/adhoc/CreateMeasuresTableTest.java
new file mode 100644 (file)
index 0000000..ede32bd
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.adhoc;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.COLUMN_BRANCH_UUID;
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.COLUMN_COMPONENT_UUID;
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.COLUMN_CREATED_AT;
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.COLUMN_JSON_VALUE;
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.COLUMN_JSON_VALUE_HASH;
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.COLUMN_UPDATED_AT;
+import static org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable.MEASURES_TABLE_NAME;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+
+public class CreateMeasuresTableTest {
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createEmpty();
+
+  private final DdlChange underTest = new CreateMeasuresTable(db.database());
+
+  @Test
+  public void execute_shouldCreateTable() throws SQLException {
+    db.assertTableDoesNotExist(MEASURES_TABLE_NAME);
+
+    underTest.execute();
+
+    db.assertTableExists(MEASURES_TABLE_NAME);
+    db.assertNoPrimaryKey(MEASURES_TABLE_NAME);
+    db.assertColumnDefinition(MEASURES_TABLE_NAME, COLUMN_COMPONENT_UUID, Types.VARCHAR, UUID_SIZE, false);
+    db.assertColumnDefinition(MEASURES_TABLE_NAME, COLUMN_BRANCH_UUID, Types.VARCHAR, UUID_SIZE, false);
+    db.assertColumnDefinition(MEASURES_TABLE_NAME, COLUMN_JSON_VALUE, Types.CLOB, null, false);
+    db.assertColumnDefinition(MEASURES_TABLE_NAME, COLUMN_JSON_VALUE_HASH, Types.BIGINT, null, false);
+    db.assertColumnDefinition(MEASURES_TABLE_NAME, COLUMN_CREATED_AT, Types.BIGINT, null, false);
+    db.assertColumnDefinition(MEASURES_TABLE_NAME, COLUMN_UPDATED_AT, Types.BIGINT, null, false);
+  }
+
+  @Test
+  public void execute_shouldBeReentrant() throws SQLException {
+    db.assertTableDoesNotExist(MEASURES_TABLE_NAME);
+
+    underTest.execute();
+    underTest.execute();
+
+    db.assertTableExists(MEASURES_TABLE_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToPortfoliosTableTest/schema.sql
new file mode 100644 (file)
index 0000000..7fa8224
--- /dev/null
@@ -0,0 +1,16 @@
+CREATE TABLE "PORTFOLIOS"(
+    "UUID" CHARACTER VARYING(40) NOT NULL,
+    "KEE" CHARACTER VARYING(400) NOT NULL,
+    "NAME" CHARACTER VARYING(2000) NOT NULL,
+    "DESCRIPTION" CHARACTER VARYING(2000),
+    "ROOT_UUID" CHARACTER VARYING(40) NOT NULL,
+    "PARENT_UUID" CHARACTER VARYING(40),
+    "PRIVATE" BOOLEAN NOT NULL,
+    "SELECTION_MODE" CHARACTER VARYING(50) NOT NULL,
+    "SELECTION_EXPRESSION" CHARACTER VARYING(4000),
+    "CREATED_AT" BIGINT NOT NULL,
+    "UPDATED_AT" BIGINT NOT NULL,
+    "BRANCH_KEY" CHARACTER VARYING(255)
+);
+ALTER TABLE "PORTFOLIOS" ADD CONSTRAINT "PK_PORTFOLIOS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PORTFOLIOS_KEE" ON "PORTFOLIOS"("KEE" NULLS FIRST);
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/AddMeasuresMigratedColumnToProjectBranchesTableTest/schema.sql
new file mode 100644 (file)
index 0000000..4b20881
--- /dev/null
@@ -0,0 +1,15 @@
+CREATE TABLE "PROJECT_BRANCHES"(
+    "UUID" CHARACTER VARYING(50) NOT NULL,
+    "PROJECT_UUID" CHARACTER VARYING(50) NOT NULL,
+    "KEE" CHARACTER VARYING(255) NOT NULL,
+    "BRANCH_TYPE" CHARACTER VARYING(12) NOT NULL,
+    "MERGE_BRANCH_UUID" CHARACTER VARYING(50),
+    "PULL_REQUEST_BINARY" BINARY LARGE OBJECT,
+    "MANUAL_BASELINE_ANALYSIS_UUID" CHARACTER VARYING(40),
+    "CREATED_AT" BIGINT NOT NULL,
+    "UPDATED_AT" BIGINT NOT NULL,
+    "EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL,
+    "NEED_ISSUE_SYNC" BOOLEAN NOT NULL
+);
+ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE" NULLS FIRST, "PROJECT_UUID" NULLS FIRST, "KEE" NULLS FIRST);
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigratedTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnPortfoliosMeasuresMigratedTest/schema.sql
new file mode 100644 (file)
index 0000000..367e045
--- /dev/null
@@ -0,0 +1,17 @@
+CREATE TABLE "PORTFOLIOS"(
+    "UUID" CHARACTER VARYING(40) NOT NULL,
+    "KEE" CHARACTER VARYING(400) NOT NULL,
+    "NAME" CHARACTER VARYING(2000) NOT NULL,
+    "DESCRIPTION" CHARACTER VARYING(2000),
+    "ROOT_UUID" CHARACTER VARYING(40) NOT NULL,
+    "PARENT_UUID" CHARACTER VARYING(40),
+    "PRIVATE" BOOLEAN NOT NULL,
+    "SELECTION_MODE" CHARACTER VARYING(50) NOT NULL,
+    "SELECTION_EXPRESSION" CHARACTER VARYING(4000),
+    "CREATED_AT" BIGINT NOT NULL,
+    "UPDATED_AT" BIGINT NOT NULL,
+    "BRANCH_KEY" CHARACTER VARYING(255),
+    "MEASURES_MIGRATED" BOOLEAN NOT NULL DEFAULT FALSE
+);
+ALTER TABLE "PORTFOLIOS" ADD CONSTRAINT "PK_PORTFOLIOS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PORTFOLIOS_KEE" ON "PORTFOLIOS"("KEE" NULLS FIRST);
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigratedTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/adhoc/CreateIndexOnProjectBranchesMeasuresMigratedTest/schema.sql
new file mode 100644 (file)
index 0000000..5e8762a
--- /dev/null
@@ -0,0 +1,16 @@
+CREATE TABLE "PROJECT_BRANCHES"(
+    "UUID" CHARACTER VARYING(50) NOT NULL,
+    "PROJECT_UUID" CHARACTER VARYING(50) NOT NULL,
+    "KEE" CHARACTER VARYING(255) NOT NULL,
+    "BRANCH_TYPE" CHARACTER VARYING(12) NOT NULL,
+    "MERGE_BRANCH_UUID" CHARACTER VARYING(50),
+    "PULL_REQUEST_BINARY" BINARY LARGE OBJECT,
+    "MANUAL_BASELINE_ANALYSIS_UUID" CHARACTER VARYING(40),
+    "CREATED_AT" BIGINT NOT NULL,
+    "UPDATED_AT" BIGINT NOT NULL,
+    "EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL,
+    "NEED_ISSUE_SYNC" BOOLEAN NOT NULL,
+    "MEASURES_MIGRATED" BOOLEAN NOT NULL DEFAULT FALSE
+);
+ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE" NULLS FIRST, "PROJECT_UUID" NULLS FIRST, "KEE" NULLS FIRST);
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/PrepareMigrationAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/PrepareMigrationAction.java
new file mode 100644 (file)
index 0000000..a89d1eb
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.ws;
+
+import com.google.common.io.Resources;
+import java.sql.SQLException;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.db.DbClient;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToPortfoliosTable;
+import org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToProjectBranchesTable;
+import org.sonar.server.platform.db.migration.adhoc.CreateIndexOnPortfoliosMeasuresMigrated;
+import org.sonar.server.platform.db.migration.adhoc.CreateIndexOnProjectBranchesMeasuresMigrated;
+import org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable;
+import org.sonar.server.user.UserSession;
+
+import static java.lang.String.format;
+
+/**
+ * Implementation of the {@code prepare_migration} action for the System WebService.
+ */
+public class PrepareMigrationAction implements SystemWsAction {
+
+  public static final String PARAM_ENABLE = "enable";
+  public static final String SYSTEM_MEASURES_MIGRATION_ENABLED = "system.measures.migration.enabled";
+  private final UserSession userSession;
+  private final DbClient dbClient;
+  private final CreateMeasuresTable createMeasuresTable;
+  private final AddMeasuresMigratedColumnToProjectBranchesTable addMeasuresMigratedColumnToProjectBranchesTable;
+  private final AddMeasuresMigratedColumnToPortfoliosTable addMeasuresMigratedColumnToPortfoliosTable;
+  private final CreateIndexOnProjectBranchesMeasuresMigrated createIndexOnProjectBranchesMeasuresMigrated;
+  private final CreateIndexOnPortfoliosMeasuresMigrated createIndexOnPortfoliosMeasuresMigrated;
+
+  public PrepareMigrationAction(UserSession userSession, DbClient dbClient, CreateMeasuresTable createMeasuresTable,
+    AddMeasuresMigratedColumnToProjectBranchesTable addMeasuresMigratedColumnToProjectBranchesTable,
+    AddMeasuresMigratedColumnToPortfoliosTable addMeasuresMigratedColumnToPortfoliosTable,
+    CreateIndexOnProjectBranchesMeasuresMigrated createIndexOnProjectBranchesMeasuresMigrated,
+    CreateIndexOnPortfoliosMeasuresMigrated createIndexOnPortfoliosMeasuresMigrated) {
+    this.userSession = userSession;
+    this.dbClient = dbClient;
+    this.createMeasuresTable = createMeasuresTable;
+    this.addMeasuresMigratedColumnToProjectBranchesTable = addMeasuresMigratedColumnToProjectBranchesTable;
+    this.addMeasuresMigratedColumnToPortfoliosTable = addMeasuresMigratedColumnToPortfoliosTable;
+    this.createIndexOnProjectBranchesMeasuresMigrated = createIndexOnProjectBranchesMeasuresMigrated;
+    this.createIndexOnPortfoliosMeasuresMigrated = createIndexOnPortfoliosMeasuresMigrated;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    NewAction action = controller.createAction("prepare_migration")
+      .setDescription("Prepare the migration to the next major version of SonarQube." +
+        "<br/>" +
+        "Sending a POST request to this URL enables the 'live_measures' table migration. " +
+        "It is strongly advised to <strong>make a database backup</strong> before invoking this WS. " +
+        "Requires system administration permission.")
+      .setSince("9.9.8")
+      .setPost(true)
+      .setHandler(this)
+      .setInternal(true)
+      .setResponseExample(Resources.getResource(this.getClass(), "example-prepare_migration.json"));
+
+    action.createParam(PARAM_ENABLE)
+      .setDescription("Set to true to enable the migration mode. Set to false to disable.")
+      .setBooleanPossibleValues()
+      .setRequired(true);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkIsSystemAdministrator();
+
+    boolean enable = request.mandatoryParamAsBoolean(PARAM_ENABLE);
+    if (enable) {
+      updateDdl();
+    }
+    updateProperty(enable);
+
+    try (JsonWriter json = response.newJsonWriter()) {
+      json.beginObject()
+        .prop("message", format("The 'live_measures' migration mode is %s", enable ? "enabled" : "disabled"))
+        .endObject();
+    }
+  }
+
+  private void updateDdl() throws SQLException {
+    createMeasuresTable.execute();
+    addMeasuresMigratedColumnToProjectBranchesTable.execute();
+    addMeasuresMigratedColumnToPortfoliosTable.execute();
+    createIndexOnProjectBranchesMeasuresMigrated.execute();
+    createIndexOnPortfoliosMeasuresMigrated.execute();
+  }
+
+  private void updateProperty(boolean enable) {
+    dbClient.propertiesDao().saveProperty(new PropertyDto().setKey(SYSTEM_MEASURES_MIGRATION_ENABLED).setValue(Boolean.toString(enable)));
+  }
+
+}
index e18d8946b6bb0e0b1b8c654623e102e6ff9c8d7c..5807d697a88a2652d728246dd59327810c907aab 100644 (file)
 package org.sonar.server.platform.ws;
 
 import org.sonar.core.platform.Module;
+import org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToPortfoliosTable;
+import org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToProjectBranchesTable;
+import org.sonar.server.platform.db.migration.adhoc.CreateIndexOnPortfoliosMeasuresMigrated;
+import org.sonar.server.platform.db.migration.adhoc.CreateIndexOnProjectBranchesMeasuresMigrated;
+import org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable;
 
 public class SystemWsModule extends Module {
 
@@ -35,6 +40,13 @@ public class SystemWsModule extends Module {
       LivenessActionSupport.class,
       LivenessAction.class,
 
+      CreateMeasuresTable.class,
+      AddMeasuresMigratedColumnToProjectBranchesTable.class,
+      AddMeasuresMigratedColumnToPortfoliosTable.class,
+      CreateIndexOnProjectBranchesMeasuresMigrated.class,
+      CreateIndexOnPortfoliosMeasuresMigrated.class,
+      PrepareMigrationAction.class,
+
       InfoAction.class,
       LogsAction.class,
       MigrateDbAction.class,
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/platform/ws/example-prepare_migration.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/platform/ws/example-prepare_migration.json
new file mode 100644 (file)
index 0000000..902e4c6
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "message": "The 'live_measures' migration mode is enabled"
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/PrepareMigrationActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/PrepareMigrationActionTest.java
new file mode 100644 (file)
index 0000000..1b6de61
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.ws;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToPortfoliosTable;
+import org.sonar.server.platform.db.migration.adhoc.AddMeasuresMigratedColumnToProjectBranchesTable;
+import org.sonar.server.platform.db.migration.adhoc.CreateIndexOnPortfoliosMeasuresMigrated;
+import org.sonar.server.platform.db.migration.adhoc.CreateIndexOnProjectBranchesMeasuresMigrated;
+import org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.sonar.server.platform.ws.PrepareMigrationAction.SYSTEM_MEASURES_MIGRATION_ENABLED;
+import static org.sonar.test.JsonAssert.assertJson;
+
+@RunWith(DataProviderRunner.class)
+public class PrepareMigrationActionTest {
+
+  public static final String PARAM_ENABLE = "enable";
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone().logIn().setSystemAdministrator();
+
+  @Rule
+  public DbTester dbTester = DbTester.create();
+
+  private final CreateMeasuresTable createMeasuresTable = mock();
+  private final AddMeasuresMigratedColumnToProjectBranchesTable addMeasuresMigratedColumnToProjectBranchesTable = mock();
+  private final AddMeasuresMigratedColumnToPortfoliosTable addMeasuresMigratedColumnToPortfoliosTable = mock();
+  private final CreateIndexOnProjectBranchesMeasuresMigrated createIndexOnProjectBranchesMeasuresMigrated = mock();
+  private final CreateIndexOnPortfoliosMeasuresMigrated createIndexOnPortfoliosMeasuresMigrated = mock();
+
+  private final PrepareMigrationAction underTest = new PrepareMigrationAction(userSessionRule, dbTester.getDbClient(), createMeasuresTable,
+    addMeasuresMigratedColumnToProjectBranchesTable, addMeasuresMigratedColumnToPortfoliosTable, createIndexOnProjectBranchesMeasuresMigrated, createIndexOnPortfoliosMeasuresMigrated);
+  private final WsActionTester tester = new WsActionTester(underTest);
+
+  @Test
+  public void should_throw_if_enable_parameter_is_missing() {
+    TestRequest request = tester.newRequest();
+
+    assertThatIllegalArgumentException()
+      .isThrownBy(request::execute)
+      .withMessage("The 'enable' parameter is missing");
+  }
+
+  @Test
+  public void verify_example() {
+    TestResponse response = tester.newRequest()
+      .setParam(PARAM_ENABLE, "true")
+      .execute();
+
+    assertJson(response.getInput()).isSimilarTo(getClass().getResource("example-prepare_migration.json"));
+  }
+
+  @Test
+  public void throws_ForbiddenException_if_user_is_not_logged_in() {
+    userSessionRule.anonymous();
+
+    TestRequest request = tester.newRequest();
+
+    assertThatExceptionOfType(ForbiddenException.class)
+      .isThrownBy(request::execute);
+  }
+
+  @Test
+  public void throws_ForbiddenException_if_user_is_not_system_admin() {
+    userSessionRule.logIn();
+
+    TestRequest request = tester.newRequest();
+
+    assertThatExceptionOfType(ForbiddenException.class)
+      .isThrownBy(request::execute);
+  }
+
+  @Test
+  @DataProvider(value = {"true", "yes"})
+  public void should_enable_migration(String enableParamValue) throws SQLException {
+    assertThat(getPropertyValue()).isNull();
+
+    TestResponse response = tester.newRequest()
+      .setParam(PARAM_ENABLE, enableParamValue)
+      .execute();
+
+    assertThat(response.getStatus()).isEqualTo(200);
+    assertThat(getPropertyValue()).isTrue();
+
+    verify(createMeasuresTable).execute();
+    verify(addMeasuresMigratedColumnToProjectBranchesTable).execute();
+    verify(addMeasuresMigratedColumnToPortfoliosTable).execute();
+    verify(createIndexOnProjectBranchesMeasuresMigrated).execute();
+    verify(createIndexOnPortfoliosMeasuresMigrated).execute();
+
+    // reentrant
+    response = tester.newRequest()
+      .setParam(PARAM_ENABLE, enableParamValue)
+      .execute();
+
+    assertThat(response.getStatus()).isEqualTo(200);
+    assertThat(getPropertyValue()).isTrue();
+  }
+
+  @Test
+  public void property_is_unchanged_if_the_migrations_failed() throws SQLException {
+    doThrow(new SQLException("Oops")).when(createMeasuresTable).execute();
+
+    TestRequest request = tester.newRequest()
+      .setParam(PARAM_ENABLE, "true");
+
+    assertThatExceptionOfType(RuntimeException.class)
+      .isThrownBy(request::execute);
+
+    assertThat(getPropertyValue()).isNull();
+  }
+
+  @Test
+  @DataProvider(value = {"false", "no"})
+  public void should_disable_migration(String disableParamValue) {
+    dbTester.getDbClient().propertiesDao().saveProperty(new PropertyDto().setKey(SYSTEM_MEASURES_MIGRATION_ENABLED).setValue("true"));
+
+    TestResponse response = tester.newRequest()
+      .setParam(PARAM_ENABLE, disableParamValue)
+      .execute();
+
+    assertThat(response.getStatus()).isEqualTo(200);
+    assertThat(getPropertyValue()).isFalse();
+
+    verifyNoInteractions(createMeasuresTable, addMeasuresMigratedColumnToPortfoliosTable, addMeasuresMigratedColumnToProjectBranchesTable,
+      createIndexOnProjectBranchesMeasuresMigrated, createIndexOnPortfoliosMeasuresMigrated);
+
+    // reentrant
+    response = tester.newRequest()
+      .setParam(PARAM_ENABLE, disableParamValue)
+      .execute();
+
+    assertThat(response.getStatus()).isEqualTo(200);
+    assertThat(getPropertyValue()).isFalse();
+  }
+
+  private Boolean getPropertyValue() {
+    PropertyDto propertyDto = dbTester.getDbClient().propertiesDao().selectGlobalProperty(SYSTEM_MEASURES_MIGRATION_ENABLED);
+    if (propertyDto == null) {
+      return null;
+    }
+    return Boolean.parseBoolean(propertyDto.getValue());
+  }
+
+}
index 0a75383e46ddc7d8ce9929a6607caa91265406b2..1249de66c191b65c654ed0e53f94486f5d77869f 100644 (file)
@@ -29,6 +29,6 @@ public class SystemWsModuleTest {
   public void verify_count_of_added_components() {
     ListContainer container = new ListContainer();
     new SystemWsModule().configure(container);
-    assertThat(container.getAddedObjects()).hasSize(15);
+    assertThat(container.getAddedObjects()).hasSize(21);
   }
 }