From: Julien Lancelot Date: Thu, 16 May 2019 08:31:00 +0000 (+0200) Subject: SONAR-12026 Migrate existing hotspots statuses X-Git-Tag: 7.8~194 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=09ab6b574f976764a312d685b173f1ac47b35aa0;p=sonarqube.git SONAR-12026 Migrate existing hotspots statuses --- diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java index dc78aa459b1..3ea4852ccf5 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java @@ -244,6 +244,9 @@ public class AbstractDbTester extends ExternalResource { } else if (value instanceof Integer) { // To be consistent, all INTEGER types are mapped as Long value = ((Integer) value).longValue(); + } else if (value instanceof Byte) { + Byte byteValue = (Byte) value; + value = byteValue.intValue(); } else if (value instanceof Timestamp) { value = new Date(((Timestamp) value).getTime()); } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78.java index 9adeaa827d6..dde78a84ef7 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78.java @@ -31,6 +31,7 @@ public class DbVersion78 implements DbVersion { .add(2701, "Add index to org_qprofile.parent_uuid", AddIndexToOrgQProfileParentUuid.class) .add(2702, "Add column webhooks.secret", AddWebhooksSecret.class) .add(2703, "Add security fields to Elasticsearch indices", AddSecurityFieldsToElasticsearchIndices.class) - .add(2704, "Add InternalComponentProperties table", CreateInternalComponentPropertiesTable.class); + .add(2704, "Add InternalComponentProperties table", CreateInternalComponentPropertiesTable.class) + .add(2707, "Update statuses of Security Hotspots", UpdateSecurityHotspotsStatuses.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatuses.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatuses.java new file mode 100644 index 00000000000..8cd5db78a77 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatuses.java @@ -0,0 +1,242 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.v78; + +import com.google.common.collect.Maps; +import java.io.Serializable; +import java.sql.SQLException; +import java.util.Map; +import java.util.Objects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.SupportsBlueGreen; +import org.sonar.server.platform.db.migration.es.MigrationEsClient; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +@SupportsBlueGreen +public class UpdateSecurityHotspotsStatuses extends DataChange { + + private static final String RESOLUTION_FIXED = "FIXED"; + private static final String RESOLUTION_WONT_FIX = "WONTFIX"; + + private static final String STATUS_OPEN = "OPEN"; + private static final String STATUS_REOPENED = "REOPENED"; + private static final String STATUS_RESOLVED = "RESOLVED"; + + private static final String STATUS_TO_REVIEW = "TOREVIEW"; + private static final String STATUS_IN_REVIEW = "INREVIEW"; + private static final String STATUS_REVIEWED = "REVIEWED"; + + private static final int RULE_TYPE_SECURITY_HOTSPOT = 4; + + private final Configuration configuration; + private final System2 system2; + private final MigrationEsClient esClient; + private final UuidFactory uuidFactory; + + public UpdateSecurityHotspotsStatuses(Database db, Configuration configuration, System2 system2, MigrationEsClient esClient, UuidFactory uuidFactory) { + super(db); + this.configuration = configuration; + this.system2 = system2; + this.esClient = esClient; + this.uuidFactory = uuidFactory; + } + + @Override + public void execute(Context context) throws SQLException { + if (configuration.getBoolean("sonar.sonarcloud.enabled").orElse(false)) { + return; + } + long now = system2.now(); + MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("security hotspots"); + massUpdate.select("select i.kee, i.status, i.resolution, i.issue_type from issues i " + + "inner join rules r on r.id = i.rule_id and r.rule_type = ? " + + "where (i.resolution is null or i.resolution in (?, ?)) and i.issue_type=? " + + // Add status check for the re-entrance, in order to not reload already migrated issues + "and i.status not in (?, ?, ?)") + .setInt(1, RULE_TYPE_SECURITY_HOTSPOT) + .setString(2, RESOLUTION_FIXED) + .setString(3, RESOLUTION_WONT_FIX) + .setInt(4, RULE_TYPE_SECURITY_HOTSPOT) + .setString(5, STATUS_TO_REVIEW) + .setString(6, STATUS_IN_REVIEW) + .setString(7, STATUS_REVIEWED); + massUpdate.update("update issues set status=?, resolution=?, updated_at=? where kee=? "); + massUpdate.update("insert into issue_changes (kee, issue_key, change_type, change_data, created_at, updated_at, issue_change_creation_date) values (?, ?, 'diff', ?, ?, ?, ?)"); + massUpdate.execute((row, update, updateIndex) -> { + String issueKey = row.getString(1); + String status = row.getString(2); + String resolution = row.getNullableString(3); + + IssueUpdate issueUpdate = new IssueUpdate(status, resolution); + FieldDiffs fieldDiffs = issueUpdate.process(); + if (!issueUpdate.isUpdated()) { + return false; + } + if (updateIndex == 0) { + update.setString(1, issueUpdate.getNewStatus()); + update.setString(2, issueUpdate.getNewResolution()); + update.setLong(3, now); + update.setString(4, issueKey); + return true; + } else { + // No changelog on OPEN issue as there was no previous state + if (!status.equals(STATUS_OPEN)) { + update.setString(1, uuidFactory.create()); + update.setString(2, issueKey); + update.setString(3, fieldDiffs.toEncodedString()); + update.setLong(4, now); + update.setLong(5, now); + update.setLong(6, now); + return true; + } + return false; + } + }); + esClient.deleteIndexes("issues"); + } + + private static class IssueUpdate { + + private static final String RESOLUTION_FIELD = "resolution"; + private static final String STATUS_FIELD = "status"; + + private final String status; + private final String resolution; + + private String newStatus; + private String newResolution; + private boolean updated; + + IssueUpdate(String status, @CheckForNull String resolution) { + this.status = status; + this.resolution = resolution; + } + + FieldDiffs process() { + if ((status.equals(STATUS_OPEN) || (status.equals(STATUS_REOPENED))) && resolution == null) { + newStatus = STATUS_TO_REVIEW; + newResolution = null; + updated = true; + } else if (status.equals(STATUS_RESOLVED) && resolution != null) { + if (resolution.equals(RESOLUTION_FIXED)) { + newStatus = STATUS_IN_REVIEW; + newResolution = null; + updated = true; + } else if (resolution.equals(RESOLUTION_WONT_FIX)) { + newStatus = STATUS_REVIEWED; + newResolution = RESOLUTION_FIXED; + updated = true; + } + } + FieldDiffs fieldDiffs = new FieldDiffs(); + fieldDiffs.setDiff(STATUS_FIELD, status, newStatus); + fieldDiffs.setDiff(RESOLUTION_FIELD, resolution, newResolution); + return fieldDiffs; + } + + String getNewStatus() { + return newStatus; + } + + String getNewResolution() { + return newResolution; + } + + boolean isUpdated() { + return updated; + } + } + + /** + * Inspired and simplified from {@link org.sonar.core.issue.FieldDiffs} + */ + static class FieldDiffs implements Serializable { + + private final Map diffs = Maps.newLinkedHashMap(); + + void setDiff(String field, @Nullable String oldValue, @Nullable String newValue) { + diffs.put(field, new Diff(oldValue, newValue)); + } + + String toEncodedString() { + StringBuilder sb = new StringBuilder(); + boolean notFirst = false; + for (Map.Entry entry : diffs.entrySet()) { + if (notFirst) { + sb.append(','); + } else { + notFirst = true; + } + sb.append(entry.getKey()); + sb.append('='); + sb.append(entry.getValue().toEncodedString()); + } + return sb.toString(); + } + + static class Diff implements Serializable { + private String oldValue; + private String newValue; + + Diff(@Nullable String oldValue, @Nullable String newValue) { + this.oldValue = oldValue; + this.newValue = newValue; + } + + private String toEncodedString() { + StringBuilder sb = new StringBuilder(); + if (oldValue != null) { + sb.append(oldValue); + sb.append('|'); + } + if (newValue != null) { + sb.append(newValue); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FieldDiffs.Diff diff = (FieldDiffs.Diff) o; + return Objects.equals(oldValue, diff.oldValue) && + Objects.equals(newValue, diff.newValue); + } + + @Override + public int hashCode() { + return Objects.hash(oldValue, newValue); + } + } + + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78Test.java index 5d44948e8a1..e126254bd4d 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78Test.java @@ -35,7 +35,7 @@ public class DbVersion78Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 5); + verifyMigrationCount(underTest, 6); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest.java new file mode 100644 index 00000000000..cefd7097832 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest.java @@ -0,0 +1,233 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.v78; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import org.apache.commons.lang.math.RandomUtils; +import org.assertj.core.groups.Tuple; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.es.MigrationEsClient; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class UpdateSecurityHotspotsStatusesTest { + + private static final long PAST = 5_000_000_000L; + private static final long NOW = 10_000_000_000L; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(UpdateSecurityHotspotsStatusesTest.class, "schema.sql"); + + private MapSettings settings = new MapSettings(); + private TestSystem2 system2 = new TestSystem2().setNow(NOW); + private MigrationEsClient esClient = mock(MigrationEsClient.class); + + private DataChange underTest = new UpdateSecurityHotspotsStatuses(db.database(), settings.asConfig(), system2, esClient, UuidFactoryFast.getInstance()); + + @Test + public void migrate_open_and_reopen_hotspots() throws SQLException { + int rule = insertRule(4); + String issue1 = insertIssue("OPEN", null, 4, rule); + String issue2 = insertIssue("REOPENED", null, 4, rule); + // Other type of issues should not be updated + String issue3 = insertIssue("OPEN", null, 1, rule); + String issue4 = insertIssue("REOPENED", null, 2, rule); + String issue5 = insertIssue("OPEN", null, 3, rule); + + underTest.execute(); + + assertIssues( + tuple(issue1, "TOREVIEW", null, 4, NOW), + tuple(issue2, "TOREVIEW", null, 4, NOW), + // Not updated + tuple(issue3, "OPEN", null, 1, PAST), + tuple(issue4, "REOPENED", null, 2, PAST), + tuple(issue5, "OPEN", null, 3, PAST)); + } + + @Test + public void migrate_resolved_as_fixed_and_wont_fix_hotspots() throws SQLException { + int rule = insertRule(4); + String issue1 = insertIssue("RESOLVED", "FIXED", 4, rule); + String issue2 = insertIssue("RESOLVED", "WONTFIX", 4, rule); + // Other type of issues should not be updated + String issue3 = insertIssue("RESOLVED", "FIXED", 1, rule); + String issue4 = insertIssue("RESOLVED", "WONTFIX", 2, rule); + String issue5 = insertIssue("RESOLVED", "WONTFIX", 3, rule); + + underTest.execute(); + + assertIssues( + tuple(issue1, "INREVIEW", null, 4, NOW), + tuple(issue2, "REVIEWED", "FIXED", 4, NOW), + // Not updated + tuple(issue3, "RESOLVED", "FIXED", 1, PAST), + tuple(issue4, "RESOLVED", "WONTFIX", 2, PAST), + tuple(issue5, "RESOLVED", "WONTFIX", 3, PAST)); + } + + @Test + public void insert_issue_changes() throws SQLException { + int rule = insertRule(4); + String issue1 = insertIssue("REOPENED", null, 4, rule); + // No changelog on OPEN issue as there was no previous state + String issue2 = insertIssue("OPEN", null, 4, rule); + String issue3 = insertIssue("RESOLVED", "FIXED", 4, rule); + String issue4 = insertIssue("RESOLVED", "WONTFIX", 4, rule); + + underTest.execute(); + + assertIssueChanges( + tuple(issue1, "diff", "status=REOPENED|TOREVIEW,resolution=", NOW, NOW, NOW), + tuple(issue3, "diff", "status=RESOLVED|INREVIEW,resolution=FIXED|", NOW, NOW, NOW), + tuple(issue4, "diff", "status=RESOLVED|REVIEWED,resolution=WONTFIX|FIXED", NOW, NOW, NOW)); + } + + @Test + public void do_not_update_vulnerabilities_coming_from_hotspot() throws SQLException { + int rule = insertRule(4); + String issue1 = insertIssue("OPEN", null, 3, rule); + + underTest.execute(); + + assertIssues(tuple(issue1, "OPEN", null, 3, PAST)); + assertNoIssueChanges(); + } + + @Test + public void do_not_update_closed_hotspots() throws SQLException { + int rule = insertRule(4); + String issue1 = insertIssue("CLOSED", "FIXED", 4, rule); + String issue2 = insertIssue("CLOSED", "REMOVED", 4, rule); + + underTest.execute(); + + assertIssues( + tuple(issue1, "CLOSED", "FIXED", 4, PAST), + tuple(issue2, "CLOSED", "REMOVED", 4, PAST)); + assertNoIssueChanges(); + } + + @Test + public void do_nothing_on_sonarcloud() throws SQLException { + settings.setProperty("sonar.sonarcloud.enabled", "true"); + int rule = insertRule(4); + String issue1 = insertIssue("OPEN", null, 4, rule); + + underTest.execute(); + + assertIssues(tuple(issue1, "OPEN", null, 4, PAST)); + assertNoIssueChanges(); + } + + @Test + public void migration_is_reentrant() throws SQLException { + int rule = insertRule(4); + String issue1 = insertIssue("OPEN", null, 4, rule); + String issue2 = insertIssue("REOPENED", null, 4, rule); + + underTest.execute(); + assertIssues( + tuple(issue1, "TOREVIEW", null, 4, NOW), + tuple(issue2, "TOREVIEW", null, 4, NOW)); + + // Set a new date for NOW in order to check that issues has not been updated again + system2.setNow(NOW + 1_000_000_000L); + underTest.execute(); + assertIssues( + tuple(issue1, "TOREVIEW", null, 4, NOW), + tuple(issue2, "TOREVIEW", null, 4, NOW)); + } + + @Test + public void issues_index_is_removed() throws SQLException { + underTest.execute(); + + verify(esClient).deleteIndexes("issues"); + } + + private void assertIssues(Tuple... expectedTuples) { + assertThat(db.select("SELECT kee, status, resolution, issue_type, updated_at FROM issues") + .stream() + .map(map -> new Tuple(map.get("KEE"), map.get("STATUS"), map.get("RESOLUTION"), map.get("ISSUE_TYPE"), map.get("UPDATED_AT"))) + .collect(toList())) + .containsExactlyInAnyOrder(expectedTuples); + } + + private void assertNoIssueChanges() { + assertThat(db.countRowsOfTable("issue_changes")).isZero(); + } + + private void assertIssueChanges(Tuple... expectedTuples) { + assertThat(db.select("SELECT issue_key, change_type, change_data, created_at, updated_at, issue_change_creation_date FROM issue_changes") + .stream() + .map(map -> new Tuple(map.get("ISSUE_KEY"), map.get("CHANGE_TYPE"), map.get("CHANGE_DATA"), map.get("CREATED_AT"), map.get("UPDATED_AT"), + map.get("ISSUE_CHANGE_CREATION_DATE"))) + .collect(toList())) + .containsExactlyInAnyOrder(expectedTuples); + } + + private String insertIssue(String status, @Nullable String resolution, int issueType, int ruleId) { + String issueKey = randomAlphabetic(3); + db.executeInsert( + "ISSUES", + "KEE", issueKey, + "STATUS", status, + "RESOLUTION", resolution, + "RULE_ID", ruleId, + "ISSUE_TYPE", issueType, + "COMPONENT_UUID", randomAlphanumeric(10), + "PROJECT_UUID", randomAlphanumeric(10), + "MANUAL_SEVERITY", false, + "CREATED_AT", PAST, + "UPDATED_AT", PAST); + return issueKey; + } + + private int insertRule(int ruleType) { + int id = RandomUtils.nextInt(); + db.executeInsert("RULES", + "ID", id, + "RULE_TYPE", ruleType, + "IS_EXTERNAL", false, + "PLUGIN_RULE_KEY", randomAlphanumeric(3), + "PLUGIN_NAME", randomAlphanumeric(3), + "SCOPE", "MAIN", + "IS_AD_HOC", false, + "CREATED_AT", PAST, + "UPDATED_AT", PAST); + return id; + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest/schema.sql new file mode 100644 index 00000000000..4e190ab8446 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest/schema.sql @@ -0,0 +1,81 @@ +CREATE TABLE "ISSUES" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(50) UNIQUE NOT NULL, + "COMPONENT_UUID" VARCHAR(50), + "PROJECT_UUID" VARCHAR(50), + "RULE_ID" INTEGER, + "SEVERITY" VARCHAR(10), + "MANUAL_SEVERITY" BOOLEAN NOT NULL, + "MESSAGE" VARCHAR(4000), + "LINE" INTEGER, + "GAP" DOUBLE, + "EFFORT" INTEGER, + "STATUS" VARCHAR(20), + "RESOLUTION" VARCHAR(20), + "CHECKSUM" VARCHAR(1000), + "REPORTER" VARCHAR(255), + "ASSIGNEE" VARCHAR(255), + "AUTHOR_LOGIN" VARCHAR(255), + "ACTION_PLAN_KEY" VARCHAR(50) NULL, + "ISSUE_ATTRIBUTES" VARCHAR(4000), + "TAGS" VARCHAR(4000), + "ISSUE_CREATION_DATE" BIGINT, + "ISSUE_CLOSE_DATE" BIGINT, + "ISSUE_UPDATE_DATE" BIGINT, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT, + "LOCATIONS" BLOB, + "ISSUE_TYPE" TINYINT, + "FROM_HOTSPOT" BOOLEAN NULL +); +CREATE UNIQUE INDEX "ISSUES_KEE" ON "ISSUES" ("KEE"); +CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES" ("COMPONENT_UUID"); +CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES" ("PROJECT_UUID"); +CREATE INDEX "ISSUES_RULE_ID" ON "ISSUES" ("RULE_ID"); +CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES" ("RESOLUTION"); +CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES" ("ASSIGNEE"); +CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES" ("ISSUE_CREATION_DATE"); +CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES" ("UPDATED_AT"); + +CREATE TABLE "ISSUE_CHANGES" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(50), + "ISSUE_KEY" VARCHAR(50) NOT NULL, + "USER_LOGIN" VARCHAR(255), + "CHANGE_TYPE" VARCHAR(40), + "CHANGE_DATA" VARCHAR(16777215), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT, + "ISSUE_CHANGE_CREATION_DATE" BIGINT +); +CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES" ("KEE"); +CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES" ("ISSUE_KEY"); + +CREATE TABLE "RULES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PLUGIN_KEY" VARCHAR(200), + "PLUGIN_RULE_KEY" VARCHAR(200) NOT NULL, + "PLUGIN_NAME" VARCHAR(255) NOT NULL, + "DESCRIPTION" VARCHAR(16777215), + "DESCRIPTION_FORMAT" VARCHAR(20), + "PRIORITY" INTEGER, + "IS_TEMPLATE" BOOLEAN DEFAULT FALSE, + "IS_EXTERNAL" BOOLEAN NOT NULL, + "IS_AD_HOC" BOOLEAN NOT NULL, + "TEMPLATE_ID" INTEGER, + "PLUGIN_CONFIG_KEY" VARCHAR(200), + "NAME" VARCHAR(200), + "STATUS" VARCHAR(40), + "LANGUAGE" VARCHAR(20), + "SCOPE" VARCHAR(20) NOT NULL, + "DEF_REMEDIATION_FUNCTION" VARCHAR(20), + "DEF_REMEDIATION_GAP_MULT" VARCHAR(20), + "DEF_REMEDIATION_BASE_EFFORT" VARCHAR(20), + "GAP_DESCRIPTION" VARCHAR(4000), + "SYSTEM_TAGS" VARCHAR(4000), + "SECURITY_STANDARDS" VARCHAR(4000), + "RULE_TYPE" TINYINT, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); +CREATE UNIQUE INDEX "RULES_REPO_KEY" ON "RULES" ("PLUGIN_NAME", "PLUGIN_RULE_KEY"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java index b326fd398f7..91ef4b62709 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java @@ -204,7 +204,7 @@ public class WebIssueStorageTest { .containsEntry("COMPONENT_UUID", issue.componentUuid()) .containsEntry("EFFORT", updated.effortInMinutes()) .containsEntry("ISSUE_ATTRIBUTES", "fox=bax") - .containsEntry("ISSUE_TYPE", (byte) 3) + .containsEntry("ISSUE_TYPE", 3) .containsEntry("KEE", issue.key()) .containsEntry("LINE", (long) updated.line()) .containsEntry("PROJECT_UUID", updated.projectUuid())