]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12026 Migrate existing hotspots statuses
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 16 May 2019 08:31:00 +0000 (10:31 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 22 May 2019 18:21:15 +0000 (20:21 +0200)
server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatuses.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v78/UpdateSecurityHotspotsStatusesTest/schema.sql [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java

index dc78aa459b10dfc683fff485c6bdce256ca4dc16..3ea4852ccf572c7c36a513375a1f5b836061e4e0 100644 (file)
@@ -244,6 +244,9 @@ public class AbstractDbTester<T extends CoreTestDb> 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());
         }
index 9adeaa827d60e601018003dd0023bded2d314527..dde78a84ef76f90034b4781c1565676c9fd9c6e9 100644 (file)
@@ -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 (file)
index 0000000..8cd5db7
--- /dev/null
@@ -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<String, Diff> 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<String, FieldDiffs.Diff> 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);
+      }
+    }
+
+  }
+
+}
index 5d44948e8a136b91cf05215fe790475f3321ec0a..e126254bd4d4e5ed66e082a4f8fdda8e70526b82 100644 (file)
@@ -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 (file)
index 0000000..cefd709
--- /dev/null
@@ -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 (file)
index 0000000..4e190ab
--- /dev/null
@@ -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");
index b326fd398f773db01041d1ddbc1a4d7329ecc520..91ef4b627094c8bec6c57ff611f7e20792bb5980 100644 (file)
@@ -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())