diff options
Diffstat (limited to 'server')
35 files changed, 502 insertions, 284 deletions
diff --git a/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java index 10ec76b6d49..737e076950e 100644 --- a/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java +++ b/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java @@ -83,7 +83,8 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { private static final Map<Integer, Integer> POSTGRES_TYPE_SUBSTITUTION = Map.of( BOOLEAN, BIT, DOUBLE, NUMERIC, - CLOB, VARCHAR); + CLOB, VARCHAR, + DECIMAL, NUMERIC); private static final Map<Integer, Integer> MSSQL_TYPE_SUBSTITUTION = Map.of( BOOLEAN, BIT, VARCHAR, NVARCHAR, @@ -93,7 +94,8 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { BOOLEAN, NUMERIC, BIGINT, NUMERIC, INTEGER, NUMERIC, - DOUBLE, NUMERIC); + DOUBLE, NUMERIC, + DECIMAL, NUMERIC); protected final T db; @@ -125,7 +127,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { public void executeDdl(String ddl) { try (Connection connection = getConnection(); - Statement stmt = connection.createStatement()) { + Statement stmt = connection.createStatement()) { stmt.execute(ddl); } catch (SQLException e) { throw new IllegalStateException("Failed to execute DDL: " + ddl, e); @@ -164,10 +166,10 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { } String sql = "insert into " + table.toLowerCase(Locale.ENGLISH) + " (" + - COMMA_JOINER.join(valuesByColumn.keySet().stream().map(t -> t.toLowerCase(Locale.ENGLISH)).toArray(String[]::new)) + - ") values (" + - COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) + - ")"; + COMMA_JOINER.join(valuesByColumn.keySet().stream().map(t -> t.toLowerCase(Locale.ENGLISH)).toArray(String[]::new)) + + ") values (" + + COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) + + ")"; executeUpdateSql(sql, valuesByColumn.values().toArray(new Object[valuesByColumn.size()])); } @@ -290,7 +292,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize, @Nullable Boolean isNullable) { try (Connection connection = getConnection(); - ResultSet rs = connection.getMetaData().getColumns(null, null, toVendorCase(table), toVendorCase(column))) { + ResultSet rs = connection.getMetaData().getColumns(null, null, toVendorCase(table), toVendorCase(column))) { boolean exists = false; while (rs.next()) { @@ -326,8 +328,8 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { public void assertColumnDoesNotExist(String table, String column) throws SQLException { try (Connection connection = getConnection(); - PreparedStatement stmt = connection.prepareStatement("select * from " + table); - ResultSet res = stmt.executeQuery()) { + PreparedStatement stmt = connection.prepareStatement("select * from " + table); + ResultSet res = stmt.executeQuery()) { assertThat(getColumnNames(res)).doesNotContain(column); } } @@ -365,7 +367,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { private void assertIndexImpl(String tableName, String indexName, boolean expectedUnique, String expectedColumn, String... expectedSecondaryColumns) { try (Connection connection = getConnection(); - ResultSet rs = connection.getMetaData().getIndexInfo(null, null, toVendorCase(tableName), false, false)) { + ResultSet rs = connection.getMetaData().getIndexInfo(null, null, toVendorCase(tableName), false, false)) { List<String> onColumns = new ArrayList<>(); while (rs.next()) { @@ -403,7 +405,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource { */ public void assertIndexDoesNotExist(String tableName, String indexName) { try (Connection connection = getConnection(); - ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) { + ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) { List<String> indices = new ArrayList<>(); while (rs.next()) { if (rs.getString("INDEX_NAME") != null) { diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index 78f221a55e4..d2d54a7a8f7 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -126,6 +126,7 @@ CREATE TABLE "ARCHITECTURE_GRAPHS"( "GRAPH_DATA" CHARACTER LARGE OBJECT NOT NULL ); ALTER TABLE "ARCHITECTURE_GRAPHS" ADD CONSTRAINT "PK_ARCHITECTURE_GRAPHS" PRIMARY KEY("UUID"); +CREATE UNIQUE NULLS NOT DISTINCT INDEX "UQ_IDX_AG_BRANCH_TYPE_SOURCE" ON "ARCHITECTURE_GRAPHS"("BRANCH_UUID" NULLS FIRST, "TYPE" NULLS FIRST, "SOURCE" NULLS FIRST); CREATE TABLE "AUDITS"( "UUID" CHARACTER VARYING(40) NOT NULL, diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java index 79dd5abe808..56acc2f70f9 100644 --- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java @@ -282,7 +282,7 @@ class MigratePortfoliosLiveMeasuresToMeasuresIT { "component_uuid", componentUuid, "branch_uuid", branch, "json_value", "{\"any\":\"thing\"}", - "json_value_hash", "1234", + "json_value_hash", 1234, "created_at", 12, "updated_at", 12); } diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTableIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTableIT.java index b8ec749e66b..8ed2cb41454 100644 --- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTableIT.java +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTableIT.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import java.sql.SQLException; import org.junit.jupiter.api.Test; diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java new file mode 100644 index 00000000000..cc1d462e536 --- /dev/null +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.version.v202502; + +import java.sql.SQLException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonar.db.MigrationDbTester; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.db.MigrationDbTester.createForMigrationStep; +import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.COLUMN_NAME_BRANCH_UUID; +import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.COLUMN_NAME_SOURCE; +import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.COLUMN_NAME_TYPE; +import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.INDEX_NAME; +import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.TABLE_NAME; + +class CreateIndexOnArchitectureGraphsIT { + + @RegisterExtension + public final MigrationDbTester db = createForMigrationStep(CreateUniqueIndexOnArchitectureGraphs.class); + private final DdlChange underTest = new CreateUniqueIndexOnArchitectureGraphs(db.database()); + + @Test + void execute_shouldCreateIndex() throws SQLException { + db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME); + underTest.execute(); + db.assertUniqueIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME_BRANCH_UUID, COLUMN_NAME_TYPE, COLUMN_NAME_SOURCE); + } + + @Test + void execute_shouldBeReentrant() throws SQLException { + db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME); + underTest.execute(); + underTest.execute(); + db.assertUniqueIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME_BRANCH_UUID, COLUMN_NAME_TYPE, COLUMN_NAME_SOURCE); + } +} diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuidTest.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuidTest.java index d7630686669..05857305b63 100644 --- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuidTest.java +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuidTest.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import java.sql.SQLException; import org.junit.jupiter.api.Test; @@ -26,10 +26,10 @@ import org.sonar.db.MigrationDbTester; import org.sonar.server.platform.db.migration.step.DdlChange; import static org.sonar.db.MigrationDbTester.createForMigrationStep; -import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_COMPONENT_UUID; -import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_UUID; -import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.INDEX_NAME; -import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.TABLE_NAME; +import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_COMPONENT_UUID; +import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_UUID; +import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.INDEX_NAME; +import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.TABLE_NAME; class CreateIndexOnScaReleasesComponentUuidTest { @RegisterExtension diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponentTest.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponentTest.java index 97f8a4d557d..ec377d5fb96 100644 --- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponentTest.java +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponentTest.java @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import java.sql.SQLException; import org.junit.jupiter.api.Test; diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java index 5b5af55fd0b..f844369bf02 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java @@ -39,7 +39,6 @@ import org.sonar.server.platform.db.migration.version.v107.DbVersion107; import org.sonar.server.platform.db.migration.version.v108.DbVersion108; import org.sonar.server.platform.db.migration.version.v202501.DbVersion202501; import org.sonar.server.platform.db.migration.version.v202502.DbVersion202502; -import org.sonar.server.platform.db.migration.version.v202503.DbVersion202503; public class MigrationConfigurationModule extends Module { @Override @@ -59,7 +58,6 @@ public class MigrationConfigurationModule extends Module { DbVersion108.class, DbVersion202501.class, DbVersion202502.class, - DbVersion202503.class, // migration steps MigrationStepRegistryImpl.class, diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTable.java index b8a0c26e52b..c44a3dc006c 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTable.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTable.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import java.sql.SQLException; import org.sonar.db.Database; diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTable.java index 4764e60d61b..bf2aab43019 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTable.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTable.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import java.sql.SQLException; import org.sonar.db.Database; diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuid.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuid.java index 85dc6395842..848d5925a9e 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuid.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuid.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import java.sql.Connection; import java.sql.SQLException; diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java new file mode 100644 index 00000000000..802396d18b1 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.version.v202502; + +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 CreateUniqueIndexOnArchitectureGraphs extends DdlChange { + + static final String TABLE_NAME = "architecture_graphs"; + static final String INDEX_NAME = "uq_idx_ag_branch_type_source"; + static final String COLUMN_NAME_BRANCH_UUID = "branch_uuid"; + static final String COLUMN_NAME_TYPE = "type"; + static final String COLUMN_NAME_SOURCE = "source"; + + public CreateUniqueIndexOnArchitectureGraphs(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + createIndex(context, connection); + } + } + + private void createIndex(Context context, Connection connection) { + if (!DatabaseUtils.indexExistsIgnoreCase(TABLE_NAME, INDEX_NAME, connection)) { + context.execute(new CreateIndexBuilder(getDialect()) + .setTable(TABLE_NAME) + .setName(INDEX_NAME) + .setUnique(true) + .addColumn(COLUMN_NAME_BRANCH_UUID, false) + .addColumn(COLUMN_NAME_TYPE, false) + .addColumn(COLUMN_NAME_SOURCE, false) + .build()); + } + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java index 0db5c95b6f3..8bbbe17ee01 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java @@ -54,6 +54,10 @@ public class DbVersion202502 implements DbVersion { .add(2025_02_015, "Add new_in_pull_request column to SCA dependencies", AddNewInPullRequestToScaDependenciesTable.class) .add(2025_02_016, "Insert default AI Codefix provider key and modelKey properties", InsertDefaultAiSuggestionProviderKeyAndModelKeyProperties.class) .add(2025_02_017, "Add table 'architecture_graphs'", CreateArchitectureGraphsTable.class) - ; + .add(2025_02_018, "Drop 'sca_releases_comp_uuid' index", DropIndexOnScaReleasesComponent.class) + .add(2025_02_019, "Create 'sca_releases_comp_uuid_uuid' index", CreateIndexOnScaReleasesComponentUuid.class) + .add(2025_02_020, "Add 'sca_dependencies.production_scope' column", AddProductionScopeToScaDependenciesTable.class) + .add(2025_02_021, "Add declared_license_expression to SCA releases", AddDeclaredLicenseExpressionToScaReleasesTable.class) + .add(2025_02_022, "Create 'uq_idx_ag_branch_type_source' for architecture graphs", CreateUniqueIndexOnArchitectureGraphs.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponent.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponent.java index 8001e821463..5cec4743767 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponent.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponent.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import org.sonar.db.Database; import org.sonar.server.platform.db.migration.step.DropIndexChange; diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java deleted file mode 100644 index c015f4a94ea..00000000000 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2025 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.version.v202503; - -import org.sonar.server.platform.db.migration.step.MigrationStepRegistry; -import org.sonar.server.platform.db.migration.version.DbVersion; - -// ignoring bad number formatting, as it's indented that we align the migration numbers to SQ versions -@SuppressWarnings("java:S3937") -public class DbVersion202503 implements DbVersion { - - /** - * We use the start of the 10.X cycle as an opportunity to align migration numbers with the SQ version number. - * Please follow this pattern: - * 2025_03_000 - * 2025_03_001 - * 2025_03_002 - */ - @Override - public void addSteps(MigrationStepRegistry registry) { - registry - .add(2025_03_000, "Drop 'sca_releases_comp_uuid' index", DropIndexOnScaReleasesComponent.class) - .add(2025_03_001, "Create 'sca_releases_comp_uuid_uuid' index", CreateIndexOnScaReleasesComponentUuid.class) - .add(2025_03_002, "Add 'sca_dependencies.production_scope' column", AddProductionScopeToScaDependenciesTable.class) - .add(2025_03_003, "Add declared_license_expression to SCA releases", AddDeclaredLicenseExpressionToScaReleasesTable.class); - } -} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java deleted file mode 100644 index cc36bc37f03..00000000000 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2025 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.version.v202503; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTableTest.java index f2ff0411c79..182fcf41ecb 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTableTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTableTest.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migration.version.v202503; +package org.sonar.server.platform.db.migration.version.v202502; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java deleted file mode 100644 index e1480dd9844..00000000000 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2025 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.version.v202503; - -import org.junit.jupiter.api.Test; - -import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty; -import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber; - -class DbVersion202503Test { - - private final DbVersion202503 underTest = new DbVersion202503(); - - @Test - void migrationNumber_starts_at_2025_03_000() { - verifyMinimumMigrationNumber(underTest, 2025_03_000); - } - - @Test - void verify_migration_is_not_empty() { - verifyMigrationNotEmpty(underTest); - } -} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java b/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java index 92ffa498023..1786a62e90f 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java @@ -20,13 +20,13 @@ package org.sonar.server.setting; import com.google.common.annotations.VisibleForTesting; +import jakarta.inject.Inject; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Properties; -import jakarta.inject.Inject; import org.apache.ibatis.exceptions.PersistenceException; import org.sonar.api.CoreProperties; import org.sonar.api.ce.ComputeEngineSide; diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java index f3479858bff..4b136272f6c 100644 --- a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java +++ b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java @@ -31,7 +31,8 @@ public enum Dimension { USER("user"), PROJECT("project"), LANGUAGE("language"), - ANALYSIS("analysis"); + ANALYSIS("analysis"), + FIX_SUGGESTION("fixsuggestion"); private final String value; diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java new file mode 100644 index 00000000000..e7b079a0b6d --- /dev/null +++ b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.telemetry.core.schema; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.sonar.telemetry.core.TelemetryDataType; + +import static org.sonar.telemetry.core.Granularity.ADHOC; + +public class FixSuggestionMetric extends InstallationMetric { + + @JsonProperty("fix_suggestion_uuid") + private String fixSuggestionUuid; + + @JsonProperty("project_uuid") + private String projectUuid; + + public FixSuggestionMetric(String key, Object value, TelemetryDataType type, String projectUuid, String fixSuggestionUuid) { + super(key, value, type, ADHOC); + this.projectUuid = projectUuid; + this.fixSuggestionUuid = fixSuggestionUuid; + } + + public String getProjectUuid() { + return projectUuid; + } + + public void setProjectUuid(String projectUuid) { + this.projectUuid = projectUuid; + } + + public String getFixSuggestionUuid() { + return fixSuggestionUuid; + } + + public void setFixSuggestionUuid(String fixSuggestionUuid) { + this.fixSuggestionUuid = fixSuggestionUuid; + } +} diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java index f80c2a971bb..69d77f3716f 100644 --- a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java +++ b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java @@ -39,11 +39,13 @@ class DimensionTest { assertEquals(Dimension.USER, Dimension.fromValue("user")); assertEquals(Dimension.PROJECT, Dimension.fromValue("project")); assertEquals(Dimension.LANGUAGE, Dimension.fromValue("language")); + assertEquals(Dimension.FIX_SUGGESTION, Dimension.fromValue("fixsuggestion")); assertEquals(Dimension.INSTALLATION, Dimension.fromValue("INSTALLATION")); assertEquals(Dimension.USER, Dimension.fromValue("USER")); assertEquals(Dimension.PROJECT, Dimension.fromValue("PROJECT")); assertEquals(Dimension.LANGUAGE, Dimension.fromValue("LANGUAGE")); + assertEquals(Dimension.FIX_SUGGESTION, Dimension.fromValue("FIXSUGGESTION")); } @Test diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java new file mode 100644 index 00000000000..4169ca95089 --- /dev/null +++ b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.telemetry.core.schema; + +import org.junit.jupiter.api.Test; +import org.sonar.telemetry.core.Granularity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.telemetry.core.TelemetryDataType.STRING; + +class FixSuggestionMetricTest { + + @Test + void getters() { + FixSuggestionMetric metric = new FixSuggestionMetric("ai_codefix.suggestion_rule_key", "rule:key", STRING, "projectUuid", "fixSuggestionUuid"); + + assertThat(metric.getKey()).isEqualTo("ai_codefix.suggestion_rule_key"); + assertThat(metric.getValue()).isEqualTo("rule:key"); + assertThat(metric.getProjectUuid()).isEqualTo("projectUuid"); + assertThat(metric.getGranularity()).isEqualTo(Granularity.ADHOC); + assertThat(metric.getType()).isEqualTo(STRING); + assertThat(metric.getFixSuggestionUuid()).isEqualTo("fixSuggestionUuid"); + } + + @Test + void setters() { + FixSuggestionMetric metric = new FixSuggestionMetric("ai_codefix.suggestion_rule_key", "rule:key", STRING, "projectUuid", "fixSuggestionUuid"); + metric.setProjectUuid("newProjectUuid"); + metric.setFixSuggestionUuid("newFixSuggestionUuid"); + + assertThat(metric.getProjectUuid()).isEqualTo("newProjectUuid"); + assertThat(metric.getFixSuggestionUuid()).isEqualTo("newFixSuggestionUuid"); + } +} diff --git a/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java index 174537211d5..51745d15781 100644 --- a/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java +++ b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java @@ -19,6 +19,8 @@ */ package org.sonar.server.plugins; +import java.util.List; +import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.sonar.api.Plugin; @@ -96,6 +98,35 @@ public class DetectPluginChangeIT { } @Test + public void detect_changes_when_forced_refresh() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + addPluginToFs("plugin1", "hash1", PluginType.BUNDLED); + addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL); + addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL); + + dbTester.executeDdl("insert into internal_properties (kee, is_empty, text_value, created_at) values ('plugin.refresh.forced', false, 'true', 12345)"); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isTrue(); + + // Ensure the force refresh flag has been deleted + assertThat(getInternalProperty("plugin.refresh.forced")).isEmpty(); + } + + @Test + public void detect_changes_when_internal_propertiy_has_false_value_should_not_refresh() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + addPluginToFs("plugin1", "hash1", PluginType.BUNDLED); + addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL); + addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL); + + dbTester.executeDdl("insert into internal_properties (kee, is_empty, text_value, created_at) values ('plugin.refresh.forced', false, 'false', 12345)"); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isFalse(); + } + + @Test public void fail_if_start_twice() { detectPluginChange.start(); assertThrows(IllegalStateException.class, detectPluginChange::start); @@ -123,4 +154,7 @@ public class DetectPluginChangeIT { pluginRepository.addPlugin(serverPlugin); } + private List<Map<String, Object>> getInternalProperty(String key) { + return dbTester.select("select * from internal_properties where kee='" + key + "'"); + } } diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java index 40f8f8036d7..af1972430f9 100644 --- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java @@ -34,6 +34,7 @@ import org.sonar.db.plugin.PluginDto; import static java.util.function.Function.identity; public class DetectPluginChange implements Startable { + public static final String FORCE_PLUGIN_RELOAD_PROPERTY = "plugin.refresh.forced"; private static final Logger LOG = Loggers.get(DetectPluginChange.class); private final ServerPluginRepository serverPluginRepository; @@ -49,7 +50,7 @@ public class DetectPluginChange implements Startable { public void start() { Preconditions.checkState(changesDetected == null, "Can only call #start() once"); Profiler profiler = Profiler.create(LOG).startInfo("Detect plugin changes"); - changesDetected = anyChange(); + changesDetected = isForcedReload() || anyChange(); if (changesDetected) { LOG.debug("Plugin changes detected"); } else { @@ -65,6 +66,17 @@ public class DetectPluginChange implements Startable { return changesDetected; } + private boolean isForcedReload() { + try (DbSession dbSession = dbClient.openSession(false)) { + boolean forceRefresh = Boolean.parseBoolean(dbClient.internalPropertiesDao().selectByKey(dbSession, FORCE_PLUGIN_RELOAD_PROPERTY).orElse("false")); + if (forceRefresh) { + dbClient.internalPropertiesDao().delete(dbSession, FORCE_PLUGIN_RELOAD_PROPERTY); + dbSession.commit(); + } + return forceRefresh; + } + } + private boolean anyChange() { try (DbSession dbSession = dbClient.openSession(false)) { Map<String, PluginDto> dbPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream() diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java new file mode 100644 index 00000000000..d4f87c46ce4 --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.startup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarRuntime; +import org.sonar.api.Startable; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +import static java.util.Arrays.asList; + +public class PropertiesDBCleaner implements Startable { + private static final Logger LOG = LoggerFactory.getLogger(PropertiesDBCleaner.class); + private final SonarRuntime runtime; + private final DbClient dbClient; + + public PropertiesDBCleaner(DbClient dbClient, SonarRuntime runtime) { + this.dbClient = dbClient; + this.runtime = runtime; + } + + @Override + public void start() { + LOG.info("Clean up properties from db"); + deleteMisraPropertyIfRequired(); + } + + private void deleteMisraPropertyIfRequired() { + String misraProperty = "sonar.earlyAccess.misra.enabled"; + SonarEdition edition = runtime.getEdition(); + try (DbSession dbSession = dbClient.openSession(false)) { + if (asList(SonarEdition.COMMUNITY, SonarEdition.DEVELOPER).contains(edition)) { + dbClient.propertiesDao().deleteGlobalProperty(misraProperty, dbSession); + dbSession.commit(); + } + } + } + + @Override + public void stop() { + // Nothing to do + } +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java new file mode 100644 index 00000000000..15c9955f853 --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.startup; + +import java.util.Objects; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarRuntime; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.property.PropertyDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +class PropertiesDBCleanerTest { + @RegisterExtension + public DbTester db = DbTester.create(); + private final DbClient dbClient = db.getDbClient(); + private final DbSession dbSession = db.getSession(); + private final SonarRuntime sonarRuntime = mock(SonarRuntime.class); + private static final String MISRA_SETTING = "sonar.earlyAccess.misra.enabled"; + + @ParameterizedTest + @ValueSource(strings = { "COMMUNITY", "DEVELOPER" }) + void should_clean_up_misra_prop_when_dev_or_community_edition(String edition) { + when(sonarRuntime.getEdition()).thenReturn(SonarEdition.valueOf(edition)); + + dbClient + .propertiesDao() + .saveProperty(dbSession, new PropertyDto() + .setKey(MISRA_SETTING) + .setValue("true"), null, null, null, null); + dbSession.commit(); + + new PropertiesDBCleaner(dbClient, sonarRuntime).start(); + assertThat(dbClient.propertiesDao().selectGlobalProperty(MISRA_SETTING)).isNull(); + } + + @ParameterizedTest + @ValueSource(strings = { "ENTERPRISE", "DATACENTER" }) + void should_not_clean_up_misra_prop_when_enterprise_or_above(String edition) { + when(sonarRuntime.getEdition()).thenReturn(SonarEdition.valueOf(edition)); + + PropertyDto prop = new PropertyDto() + .setKey(MISRA_SETTING) + .setValue("true"); + dbClient + .propertiesDao() + .saveProperty(dbSession, prop, null, null, null, null); + dbSession.commit(); + + new PropertiesDBCleaner(dbClient, sonarRuntime).start(); + assertThat(Objects.requireNonNull(dbClient.propertiesDao().selectGlobalProperty(MISRA_SETTING)).getValue()).isEqualTo(prop.getValue()); + } +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java index e9162a0c1f3..f8c11a99d6e 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java @@ -21,7 +21,7 @@ package org.sonar.server.v2; public class WebApiEndpoints { public static final String JSON_MERGE_PATCH_CONTENT_TYPE = "application/merge-patch+json"; - public static final String INTERNAL = "internal"; + public static final String INTERNAL = "x-sonar-internal"; public static final String SYSTEM_DOMAIN = "/system"; public static final String LIVENESS_ENDPOINT = SYSTEM_DOMAIN + "/liveness"; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java index 95a2e80093a..0c1e7a36271 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java @@ -33,6 +33,7 @@ import org.sonar.db.component.ProjectData; import org.sonar.db.component.SnapshotDto; import org.sonar.db.measure.MeasureDto; import org.sonar.db.metric.MetricDto; +import org.sonar.db.permission.GlobalPermission; import org.sonar.server.component.TestComponentFinder; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -50,6 +51,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.measures.CoreMetrics.RELIABILITY_ISSUES; import static org.sonar.api.utils.DateUtils.parseDateTime; +import static org.sonar.api.web.UserRole.SCAN; import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; import static org.sonar.db.component.BranchType.PULL_REQUEST; @@ -107,6 +109,32 @@ public class ComponentActionIT { } @Test + public void user_with_project_scan_permission_is_allowed_to_get_project_measures() { + ProjectData projectData = db.components().insertPrivateProject(); + ComponentDto mainBranch = projectData.getMainBranchComponent(); + userSession.addProjectPermission(SCAN, projectData.getProjectDto()) + .registerBranches(projectData.getMainBranchDto()); + MetricDto metric = db.measures().insertMetric(m -> m.setValueType("INT")); + + ComponentWsResponse response = newRequest(mainBranch.getKey(), metric.getKey()); + + assertThat(response.getMetrics().getMetricsCount()).isOne(); + } + + @Test + public void user_with_global_scan_permission_is_allowed_to_get_project_status() { + ProjectData projectData = db.components().insertPrivateProject(); + ComponentDto mainBranch = projectData.getMainBranchComponent(); + userSession.addPermission(GlobalPermission.SCAN); + + MetricDto metric = db.measures().insertMetric(m -> m.setValueType("INT")); + + ComponentWsResponse response = newRequest(mainBranch.getKey(), metric.getKey()); + + assertThat(response.getMetrics().getMetricsCount()).isOne(); + } + + @Test public void without_additional_fields() { ProjectData projectData = db.components().insertPrivateProject(); ComponentDto mainBranch = projectData.getMainBranchComponent(); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java index 8206511d176..af594642a2e 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java @@ -21,30 +21,18 @@ package org.sonar.server.qualityprofile.ws; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.sonar.api.config.Configuration; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.System2; -import org.sonar.api.utils.Version; -import org.sonar.core.platform.SonarQubeVersion; import org.sonar.core.util.UuidFactoryFast; -import org.sonar.core.util.UuidFactoryImpl; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.rule.RuleDto; -import org.sonar.db.rule.RuleTesting; import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.QProfileFactoryImpl; -import org.sonar.server.qualityprofile.QProfileRules; -import org.sonar.server.qualityprofile.QProfileRulesImpl; -import org.sonar.server.qualityprofile.builtin.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; -import org.sonar.server.rule.index.RuleIndex; -import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; @@ -56,7 +44,6 @@ import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; import static org.sonar.db.permission.GlobalPermission.SCAN; import static org.sonar.server.language.LanguageTesting.newLanguages; @@ -64,9 +51,6 @@ import static org.sonar.server.language.LanguageTesting.newLanguages; class CreateActionIT { private static final String XOO_LANGUAGE = "xoo"; - private static final RuleDto RULE = RuleTesting.newXooX1() - .setSeverity("MINOR") - .setLanguage(XOO_LANGUAGE); @RegisterExtension private final DbTester db = DbTester.create(); @@ -75,20 +59,12 @@ class CreateActionIT { @RegisterExtension private final UserSessionRule userSession = UserSessionRule.standalone(); - private final Configuration config = mock(Configuration.class); private final DbClient dbClient = db.getDbClient(); private final DbSession dbSession = db.getSession(); - private final RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE, config); - private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbClient); private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client()); - private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); - private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3)); - private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, dbClient, UuidFactoryImpl.INSTANCE, null, userSession, mock(Configuration.class), - sonarQubeVersion); - private final QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService); private final CreateAction underTest = new CreateAction(dbClient, new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), System2.INSTANCE, activeRuleIndexer), - newLanguages(XOO_LANGUAGE), userSession, activeRuleIndexer); + newLanguages(XOO_LANGUAGE), userSession); private WsActionTester ws = new WsActionTester(underTest); @@ -153,12 +129,6 @@ class CreateActionIT { assertThat(response.getMediaType()).isEqualTo(MediaTypes.JSON); } - private void insertRule(RuleDto ruleDto) { - dbClient.ruleDao().insert(dbSession, ruleDto); - dbSession.commit(); - ruleIndexer.commitAndIndex(dbSession, ruleDto.getUuid()); - } - private CreateWsResponse executeRequest(String name, String language) { TestRequest request = ws.newRequest() .setParam("name", name) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java index 0f52c689e69..f57d384d416 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java @@ -42,6 +42,7 @@ import org.sonar.db.component.SnapshotDto; import org.sonar.db.measure.MeasureDto; import org.sonar.db.metric.MetricDto; import org.sonar.db.metric.MetricDtoFunctions; +import org.sonar.db.permission.GlobalPermission; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.user.UserSession; @@ -66,6 +67,7 @@ import static org.sonar.server.measure.ws.ComponentResponseCommon.addMetricToRes import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter; import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriod.snapshotToWsPeriods; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; @@ -88,10 +90,15 @@ public class ComponentAction implements MeasuresWsAction { public void define(WebService.NewController context) { WebService.NewAction action = context.createAction(ACTION_COMPONENT) .setDescription("Return component with specified measures.<br>" + - "Requires the following permission: 'Browse' on the project of specified component.") + "Requires one of the following permissions:" + + "<ul>" + + "<li>'Browse' on the project of the specified component</li>" + + "<li>'Execute Analysis' on the project of the specified component</li>" + + "</ul>") .setResponseExample(getClass().getResource("component-example.json")) .setSince("5.4") .setChangelog( + new Change("2025.2", "The 'Execute Analysis' permission also allows to access the endpoint"), new Change("10.8", format("The following metrics are not deprecated anymore: %s", MeasuresWsModule.getUndeprecatedMetricsinSonarQube108())), new Change("10.8", String.format("Added new accepted values for the 'metricKeys' param: %s", @@ -282,7 +289,11 @@ public class ComponentAction implements MeasuresWsAction { } private void checkPermissions(ComponentDto baseComponent) { - userSession.checkComponentPermission(UserRole.USER, baseComponent); + if (!userSession.hasComponentPermission(UserRole.USER, baseComponent) && + !userSession.hasComponentPermission(UserRole.SCAN, baseComponent) && + !userSession.hasPermission(GlobalPermission.SCAN)) { + throw insufficientPrivilegesException(); + } } private static class ComponentRequest { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java deleted file mode 100644 index 2dadd2d8015..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2025 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.qualityprofile; - -import java.util.ArrayList; -import java.util.List; -import org.sonar.db.qualityprofile.QProfileDto; - -public class QProfileResult { - - private List<String> warnings; - private List<String> infos; - - private QProfileDto profile; - - private List<ActiveRuleChange> changes; - - public QProfileResult() { - warnings = new ArrayList<>(); - infos = new ArrayList<>(); - changes = new ArrayList<>(); - } - - public List<String> warnings() { - return warnings; - } - - public QProfileResult addWarnings(List<String> warnings) { - this.warnings.addAll(warnings); - return this; - } - - public List<String> infos() { - return infos; - } - - public QProfileResult addInfos(List<String> infos) { - this.infos.addAll(infos); - return this; - } - - public QProfileDto profile() { - return profile; - } - - public QProfileResult setProfile(QProfileDto profile) { - this.profile = profile; - return this; - } - - public List<ActiveRuleChange> getChanges() { - return changes; - } - - public QProfileResult addChanges(List<ActiveRuleChange> changes) { - this.changes.addAll(changes); - return this; - } - - public QProfileResult add(QProfileResult result) { - warnings.addAll(result.warnings()); - infos.addAll(result.infos()); - changes.addAll(result.getChanges()); - return this; - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java index cc833d15e83..2c391027a21 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java @@ -29,9 +29,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.server.qualityprofile.QProfileFactory; -import org.sonar.server.qualityprofile.QProfileResult; import org.sonar.server.qualityprofile.builtin.QProfileName; -import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Qualityprofiles.CreateWsResponse; @@ -51,15 +49,13 @@ public class CreateAction implements QProfileWsAction { private final QProfileFactory profileFactory; private final Languages languages; private final UserSession userSession; - private final ActiveRuleIndexer activeRuleIndexer; public CreateAction(DbClient dbClient, QProfileFactory profileFactory, Languages languages, - UserSession userSession, ActiveRuleIndexer activeRuleIndexer) { + UserSession userSession) { this.dbClient = dbClient; this.profileFactory = profileFactory; this.languages = languages; this.userSession = userSession; - this.activeRuleIndexer = activeRuleIndexer; } @Override @@ -88,19 +84,17 @@ public class CreateAction implements QProfileWsAction { @Override public void handle(Request request, Response response) throws Exception { userSession.checkLoggedIn(); + userSession.checkPermission(ADMINISTER_QUALITY_PROFILES); try (DbSession dbSession = dbClient.openSession(false)) { - userSession.checkPermission(ADMINISTER_QUALITY_PROFILES); CreateRequest createRequest = toRequest(request); writeProtobuf(doHandle(dbSession, createRequest), request, response); } } private CreateWsResponse doHandle(DbSession dbSession, CreateRequest createRequest) { - QProfileResult result = new QProfileResult(); QProfileDto profile = profileFactory.checkAndCreateCustom(dbSession, QProfileName.createFor(createRequest.getLanguage(), createRequest.getName())); - result.setProfile(profile); - activeRuleIndexer.commitAndIndex(dbSession, result.getChanges()); - return buildResponse(result); + dbSession.commit(); + return buildResponse(profile); } private static CreateRequest toRequest(Request request) { @@ -110,21 +104,15 @@ public class CreateAction implements QProfileWsAction { return builder.build(); } - private CreateWsResponse buildResponse(QProfileResult result) { - String language = result.profile().getLanguage(); + private CreateWsResponse buildResponse(QProfileDto profile) { + String language = profile.getLanguage(); CreateWsResponse.QualityProfile.Builder builder = CreateWsResponse.QualityProfile.newBuilder() - .setKey(result.profile().getKee()) - .setName(result.profile().getName()) + .setKey(profile.getKee()) + .setName(profile.getName()) .setLanguage(language) - .setLanguageName(languages.get(result.profile().getLanguage()).getName()) + .setLanguageName(languages.get(profile.getLanguage()).getName()) .setIsDefault(false) .setIsInherited(false); - if (!result.infos().isEmpty()) { - builder.getInfosBuilder().addAllInfos(result.infos()); - } - if (!result.warnings().isEmpty()) { - builder.getWarningsBuilder().addAllWarnings(result.warnings()); - } return CreateWsResponse.newBuilder().setProfile(builder.build()).build(); } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json index b154d6233ae..a1f54c5aa82 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json @@ -6,11 +6,5 @@ "languageName" : "Java", "name" : "My New Profile", "key" : "AU-TpxcA-iU5OvuD2FL1" - }, - "warnings" : [ - "Unable to import unknown PMD rule 'rulesets/java/strings.xml'", - "Unable to import unknown PMD rule 'rulesets/java/basic.xml/UnnecessaryConversionTemporary'", - "Unable to import unknown PMD rule 'rulesets/java/basic.xml/EmptyCatchBlock'", - "Unable to import unknown PMD rule 'rulesets/java/braces.xml'" - ] + } } diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java index 6d29800e273..8d6253b5e58 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java @@ -31,6 +31,7 @@ import org.sonar.server.platform.serverid.ServerIdModule; import org.sonar.server.plugins.DetectPluginChange; import org.sonar.server.setting.DatabaseSettingLoader; import org.sonar.server.setting.DatabaseSettingsEnabler; +import org.sonar.server.startup.PropertiesDBCleaner; import static org.sonar.core.extension.CoreExtensionsInstaller.noAdditionalSideFilter; import static org.sonar.core.extension.PlatformLevelPredicates.hasPlatformLevel; @@ -49,6 +50,7 @@ public class PlatformLevel3 extends PlatformLevel { NoopDatabaseMigrationImpl.class, new ServerIdModule(), ServerImpl.class, + PropertiesDBCleaner.class, DatabaseSettingLoader.class, DatabaseSettingsEnabler.class, UriReader.class, |