]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19197 Add code_variants to issues table
authorEric Giffon <eric.giffon@sonarsource.com>
Wed, 3 May 2023 10:33:31 +0000 (12:33 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 16 May 2023 20:02:49 +0000 (20:02 +0000)
15 files changed:
gradle.properties
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DbVersion101.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTableTest/schema.sql [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java

index d8fcfd18a4ecc8a7ba142713b0138f9a61af4669..54e29918a62d291d169cb445672a6cabc086df49 100644 (file)
@@ -1,6 +1,6 @@
 group=org.sonarsource.sonarqube
 version=10.1
-pluginApiVersion=9.16.0.560
+pluginApiVersion=9.17.0.587
 description=Open source platform for continuous inspection of code quality
 projectTitle=SonarQube
 org.gradle.jvmargs=-Xmx2048m
index 8099aaf130c9dfca330939515a909cb655ab37d0..f9832c2a662014f7bd2caef5966ead28acaa7b4a 100644 (file)
@@ -122,6 +122,8 @@ public class IssueDaoIT {
     assertThat(issue.getLocations()).isNull();
     assertThat(issue.parseLocations()).isNull();
     assertThat(issue.isExternal()).isTrue();
+    assertThat(issue.getTags()).containsOnly("tag1", "tag2");
+    assertThat(issue.getCodeVariants()).containsOnly("variant1", "variant2");
     assertFalse(issue.isQuickFixAvailable());
   }
 
@@ -590,6 +592,8 @@ public class IssueDaoIT {
     dto.setIssueCreationTime(1_450_000_000_000L);
     dto.setIssueUpdateTime(1_450_000_000_000L);
     dto.setIssueCloseTime(1_450_000_000_000L);
+    dto.setTags(Set.of("tag1", "tag2"));
+    dto.setCodeVariants(Set.of("variant1", "variant2"));
     return dto;
   }
 
index 4a9cb8afe1100e6dd2226b1e5e732d2fa07606e1..308c4c81c7d4a0facf7ba6e67673f57eff490b34 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.IntStream;
 import org.apache.ibatis.session.ResultContext;
@@ -108,6 +109,8 @@ public class IssueMapperIT {
     assertThat(result.getIssueCloseTime()).isEqualTo(1_403_000_000_000L);
     assertThat(result.getCreatedAt()).isEqualTo(1_400_000_000_000L);
     assertThat(result.getUpdatedAt()).isEqualTo(1_500_000_000_000L);
+    assertThat(result.getTags()).containsOnly("tag1", "tag2");
+    assertThat(result.getCodeVariants()).containsOnly("variant1", "variant2");
   }
 
   @Test
@@ -132,6 +135,8 @@ public class IssueMapperIT {
     update.setAssigneeUuid("karadoc");
     update.setChecksum("123456789");
     update.setMessage("the message");
+    update.setTags(Set.of("tag3", "tag4"));
+    update.setCodeVariants(Set.of("variant3", "variant4"));
 
     update.setIssueCreationTime(1_550_000_000_000L);
     update.setIssueUpdateTime(1_550_000_000_000L);
@@ -165,6 +170,8 @@ public class IssueMapperIT {
     assertThat(result.getIssueCloseTime()).isEqualTo(1_550_000_000_000L);
     assertThat(result.getCreatedAt()).isEqualTo(1_400_000_000_000L);
     assertThat(result.getUpdatedAt()).isEqualTo(1_550_000_000_000L);
+    assertThat(result.getTags()).containsOnly("tag3", "tag4");
+    assertThat(result.getCodeVariants()).containsOnly("variant3", "variant4");
   }
 
   @Test
@@ -178,7 +185,9 @@ public class IssueMapperIT {
       .setGap(1.12d)
       .setEffort(50L)
       .setIssueUpdateTime(1_600_000_000_000L)
-      .setUpdatedAt(1_600_000_000_000L);
+      .setUpdatedAt(1_600_000_000_000L)
+      .setTags(Set.of("tag2", "tag3"))
+      .setCodeVariants(Set.of("variant2", "variant3"));
 
     // selected after last update -> ok
     dto.setSelectedAt(1500000000000L);
@@ -196,6 +205,8 @@ public class IssueMapperIT {
     assertThat(result.getEffort()).isEqualTo(50L);
     assertThat(result.getIssueUpdateTime()).isEqualTo(1_600_000_000_000L);
     assertThat(result.getUpdatedAt()).isEqualTo(1_600_000_000_000L);
+    assertThat(result.getTags()).containsOnly("tag2", "tag3");
+    assertThat(result.getCodeVariants()).containsOnly("variant2", "variant3");
   }
 
   @Test
@@ -209,7 +220,9 @@ public class IssueMapperIT {
       .setGap(1.12d)
       .setEffort(50L)
       .setIssueUpdateTime(1_600_000_000_000L)
-      .setUpdatedAt(1_600_000_000_000L);
+      .setUpdatedAt(1_600_000_000_000L)
+      .setTags(Set.of("tag2", "tag3"))
+      .setCodeVariants(Set.of("variant2", "variant3"));
 
     // selected before last update -> ko
     dto.setSelectedAt(1400000000000L);
@@ -228,6 +241,8 @@ public class IssueMapperIT {
     assertThat(result.getEffort()).isEqualTo(10L);
     assertThat(result.getIssueUpdateTime()).isEqualTo(1_402_000_000_000L);
     assertThat(result.getUpdatedAt()).isEqualTo(1_500_000_000_000L);
+    assertThat(result.getTags()).containsOnly("tag1", "tag2");
+    assertThat(result.getCodeVariants()).containsOnly("variant1", "variant2");
   }
 
   @Test
@@ -539,7 +554,9 @@ public class IssueMapperIT {
       .setIssueUpdateTime(1_402_000_000_000L)
       .setIssueCloseTime(1_403_000_000_000L)
       .setCreatedAt(1_400_000_000_000L)
-      .setUpdatedAt(1_500_000_000_000L);
+      .setUpdatedAt(1_500_000_000_000L)
+      .setTags(Set.of("tag1", "tag2"))
+      .setCodeVariants(Set.of("variant1", "variant2"));
   }
 
   private static class RecorderResultHandler implements ResultHandler<IssueDto> {
index 10a8a1620c4149887754601617a9d11bae7076df..d36c839314b85913ddc8818a1fe2e557355fbf18 100644 (file)
@@ -48,9 +48,9 @@ import static org.sonar.api.utils.DateUtils.longToDate;
 public final class IssueDto implements Serializable {
 
   public static final int AUTHOR_MAX_SIZE = 255;
-  private static final char TAGS_SEPARATOR = ',';
-  private static final Joiner TAGS_JOINER = Joiner.on(TAGS_SEPARATOR).skipNulls();
-  private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+  private static final char STRING_LIST_SEPARATOR = ',';
+  private static final Joiner STRING_LIST_JOINER = Joiner.on(STRING_LIST_SEPARATOR).skipNulls();
+  private static final Splitter STRING_LIST_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
 
   private int type;
   private String kee;
@@ -97,6 +97,7 @@ public final class IssueDto implements Serializable {
   private String projectKey;
   private String filePath;
   private String tags;
+  private String codeVariants;
   // populate only when retrieving closed issue for issue tracking
   private String closedChangeData;
 
@@ -139,6 +140,7 @@ public final class IssueDto implements Serializable {
       .setSelectedAt(issue.selectedAt())
       .setQuickFixAvailable(issue.isQuickFixAvailable())
       .setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
+      .setCodeVariants(issue.codeVariants())
 
       // technical dates
       .setCreatedAt(now)
@@ -186,6 +188,7 @@ public final class IssueDto implements Serializable {
       .setSelectedAt(issue.selectedAt())
       .setQuickFixAvailable(issue.isQuickFixAvailable())
       .setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
+      .setCodeVariants(issue.codeVariants())
 
       // technical date
       .setUpdatedAt(now);
@@ -625,14 +628,14 @@ public final class IssueDto implements Serializable {
   }
 
   public Set<String> getTags() {
-    return ImmutableSet.copyOf(TAGS_SPLITTER.split(tags == null ? "" : tags));
+    return ImmutableSet.copyOf(STRING_LIST_SPLITTER.split(tags == null ? "" : tags));
   }
 
   public IssueDto setTags(@Nullable Collection<String> tags) {
     if (tags == null || tags.isEmpty()) {
       setTagsString(null);
     } else {
-      setTagsString(TAGS_JOINER.join(tags));
+      setTagsString(STRING_LIST_JOINER.join(tags));
     }
     return this;
   }
@@ -647,6 +650,30 @@ public final class IssueDto implements Serializable {
     return tags;
   }
 
+  public Set<String> getCodeVariants() {
+    return ImmutableSet.copyOf(STRING_LIST_SPLITTER.split(codeVariants == null ? "" : codeVariants));
+  }
+
+  public String getCodeVariantsString() {
+    return codeVariants;
+  }
+
+  public IssueDto setCodeVariants(@Nullable Collection<String> codeVariants) {
+    if (codeVariants == null || codeVariants.isEmpty()) {
+      setCodeVariantsString(null);
+    } else {
+      setCodeVariantsString(STRING_LIST_JOINER.join(codeVariants));
+    }
+    return this;
+  }
+
+  public IssueDto setCodeVariantsString(@Nullable String codeVariants) {
+    checkArgument(codeVariants == null || codeVariants.length() <= 4000,
+      "Value is too long for column ISSUES.CODE_VARIANTS: %codeVariants", codeVariants);
+    this.codeVariants = codeVariants;
+    return this;
+  }
+
   @CheckForNull
   public byte[] getLocations() {
     return locations;
@@ -761,6 +788,7 @@ public final class IssueDto implements Serializable {
     issue.setIsFromExternalRuleEngine(isExternal);
     issue.setQuickFixAvailable(quickFixAvailable);
     issue.setIsNewCodeReferenceIssue(isNewCodeReferenceIssue);
+    issue.setCodeVariants(getCodeVariants());
     return issue;
   }
 }
index 83ea44866933155822fe0b5e6f6da9dc0d2bc58f..96b7ac7cc1b955cc0a963e13822f8f60284dc454 100644 (file)
@@ -39,6 +39,7 @@
     i.project_uuid as projectUuid,
     i.issue_type as type,
     i.quick_fix_available as quickFixAvailable,
+    i.code_variants as codeVariantsString,
     <include refid="isNewCodeReferenceIssue"/>
   </sql>
 
@@ -68,7 +69,8 @@
     i.component_uuid,
     i.project_uuid,
     i.issue_type,
-    i.quick_fix_available
+    i.quick_fix_available,
+    i.code_variants
   </sql>
 
   <sql id="isNewCodeReferenceIssue" databaseId="mssql">
     INSERT INTO issues (kee, rule_uuid, severity, manual_severity,
     message, message_formattings, line, locations, gap, effort, status, tags, rule_description_context_key,
     resolution, checksum, assignee, author_login, issue_creation_date, issue_update_date,
-    issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type, quick_fix_available)
+    issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type, quick_fix_available, code_variants)
     VALUES (
     #{kee,jdbcType=VARCHAR},
     #{ruleUuid,jdbcType=VARCHAR},
     #{issueCreationTime,jdbcType=BIGINT},#{issueUpdateTime,jdbcType=BIGINT}, #{issueCloseTime,jdbcType=BIGINT},
     #{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT},
     #{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER},
-    #{quickFixAvailable, jdbcType=BOOLEAN})
+    #{quickFixAvailable, jdbcType=BOOLEAN},
+    #{codeVariantsString,jdbcType=VARCHAR})
   </insert>
 
   <insert id="insertAsNewCodeOnReferenceBranch" parameterType="NewCodeReferenceIssue" useGeneratedKeys="false">
     issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
     issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
     updated_at=#{updatedAt,jdbcType=BIGINT},
-    issue_type=#{type,jdbcType=INTEGER}
+    issue_type=#{type,jdbcType=INTEGER},
+    code_variants=#{codeVariantsString,jdbcType=VARCHAR}
     where kee = #{kee}
   </update>
 
     issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
     issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
     updated_at=#{updatedAt,jdbcType=BIGINT},
-    issue_type=#{type,jdbcType=INTEGER}
+    issue_type=#{type,jdbcType=INTEGER},
+    code_variants=#{codeVariantsString,jdbcType=VARCHAR}
     where kee = #{kee} and updated_at &lt;= #{selectedAt}
   </update>
 
   </select>
 
 </mapper>
-
index 8ceb69f5c2f1d4fadb3c83be13bd010dca9e423e..b2bd8e58ffe4d2f32768ff73e928f7fd1f16d992 100644 (file)
@@ -431,7 +431,8 @@ CREATE TABLE "ISSUES"(
     "FROM_HOTSPOT" BOOLEAN,
     "QUICK_FIX_AVAILABLE" BOOLEAN,
     "RULE_DESCRIPTION_CONTEXT_KEY" CHARACTER VARYING(50),
-    "MESSAGE_FORMATTINGS" BINARY LARGE OBJECT
+    "MESSAGE_FORMATTINGS" BINARY LARGE OBJECT,
+    "CODE_VARIANTS" CHARACTER VARYING(4000)
 );
 ALTER TABLE "ISSUES" ADD CONSTRAINT "PK_ISSUES" PRIMARY KEY("KEE");
 CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES"("ASSIGNEE" NULLS FIRST);
index e7ecde672662bb72f4332dd1cfb1e2bb5ea72830..977e644dbe1d264d38aa4486ad2d6657cf99934f 100644 (file)
@@ -144,6 +144,39 @@ public class IssueDtoTest {
     assertThat(dto.getTags()).isEmpty();
   }
 
+  @Test
+  public void setCodeVariants_shouldReturnCodeVariants() {
+    IssueDto dto = new IssueDto();
+    assertThat(dto.getCodeVariants()).isEmpty();
+    assertThat(dto.getCodeVariantsString()).isNull();
+
+    dto.setCodeVariants(Arrays.asList("variant1", "variant2", "variant3"));
+    assertThat(dto.getCodeVariants()).containsOnly("variant1", "variant2", "variant3");
+    assertThat(dto.getCodeVariantsString()).isEqualTo("variant1,variant2,variant3");
+
+    dto.setCodeVariants(null);
+    assertThat(dto.getCodeVariants()).isEmpty();
+    assertThat(dto.getCodeVariantsString()).isNull();
+
+    dto.setCodeVariants(List.of());
+    assertThat(dto.getCodeVariants()).isEmpty();
+    assertThat(dto.getCodeVariantsString()).isNull();
+  }
+
+  @Test
+  public void setCodeVariantsString_shouldReturnCodeVariants() {
+    IssueDto dto = new IssueDto();
+
+    dto.setCodeVariantsString("variant1, variant2 ,,variant4");
+    assertThat(dto.getCodeVariants()).containsOnly("variant1", "variant2", "variant4");
+
+    dto.setCodeVariantsString(null);
+    assertThat(dto.getCodeVariants()).isEmpty();
+
+    dto.setCodeVariantsString("");
+    assertThat(dto.getCodeVariants()).isEmpty();
+  }
+
   @Test
   public void toDtoForComputationInsert_givenDefaultIssueWithAllFields_returnFullIssueDto() {
     long now = System.currentTimeMillis();
@@ -162,8 +195,8 @@ public class IssueDtoTest {
       IssueDto::getGap, IssueDto::getEffort, IssueDto::getResolution, IssueDto::getStatus, IssueDto::getSeverity)
       .containsExactly(1, "message", 1.0, 1L, Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED, "BLOCKER");
 
-    assertThat(issueDto).extracting(IssueDto::getTags, IssueDto::getAuthorLogin)
-      .containsExactly(Set.of("todo"), "admin");
+    assertThat(issueDto).extracting(IssueDto::getTags, IssueDto::getCodeVariants, IssueDto::getAuthorLogin)
+      .containsExactly(Set.of("todo"), Set.of("variant1", "variant2"), "admin");
 
     assertThat(issueDto).extracting(IssueDto::isManualSeverity, IssueDto::getChecksum, IssueDto::getAssigneeUuid,
       IssueDto::isExternal, IssueDto::getComponentUuid, IssueDto::getComponentKey,
@@ -193,8 +226,8 @@ public class IssueDtoTest {
       IssueDto::getGap, IssueDto::getEffort, IssueDto::getResolution, IssueDto::getStatus, IssueDto::getSeverity)
       .containsExactly(1, "message", 1.0, 1L, Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED, "BLOCKER");
 
-    assertThat(issueDto).extracting(IssueDto::getTags, IssueDto::getAuthorLogin)
-      .containsExactly(Set.of("todo"), "admin");
+    assertThat(issueDto).extracting(IssueDto::getTags, IssueDto::getCodeVariants, IssueDto::getAuthorLogin)
+      .containsExactly(Set.of("todo"), Set.of("variant1", "variant2"), "admin");
 
     assertThat(issueDto).extracting(IssueDto::isManualSeverity, IssueDto::getChecksum, IssueDto::getAssigneeUuid,
       IssueDto::isExternal, IssueDto::getComponentUuid, IssueDto::getComponentKey, IssueDto::getProjectUuid, IssueDto::getProjectKey)
@@ -233,7 +266,8 @@ public class IssueDtoTest {
       .setSelectedAt(dateNow.getTime())
       .setQuickFixAvailable(true)
       .setIsNewCodeReferenceIssue(true)
-      .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
+      .setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
+      .setCodeVariants(List.of("variant1", "variant2"));
     return defaultIssue;
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTable.java
new file mode 100644 (file)
index 0000000..abf5751
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v101;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.def.ColumnDef;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddCodeVariantsColumnInIssuesTable extends DdlChange {
+
+  private static final String TABLE_NAME = "issues";
+  private static final String COLUMN_NAME = "code_variants";
+  private static final int COLUMN_SIZE = 4000;
+
+  public AddCodeVariantsColumnInIssuesTable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      if (!DatabaseUtils.tableColumnExists(connection, TABLE_NAME, COLUMN_NAME)) {
+        ColumnDef columnDef = VarcharColumnDef.newVarcharColumnDefBuilder()
+          .setColumnName(COLUMN_NAME)
+          .setLimit(COLUMN_SIZE)
+          .setIsNullable(true)
+          .build();
+        context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME).addColumn(columnDef).build());
+      }
+    }
+  }
+}
index 684da4ea0b2b92db055a972ffb979a779f65e7c1..187036c4be21e7f368b99f6387e53a50dbfa0ff4 100644 (file)
@@ -57,6 +57,7 @@ public class DbVersion101 implements DbVersion {
       .add(10_1_013, "Increase size of 'ce_queue.task_type' from 15 to 40 characters", IncreaseTaskTypeColumnSizeInCeQueue.class)
       .add(10_1_014, "Increase size of 'ce_activity.task_type' from 15 to 40 characters", IncreaseTaskTypeColumnSizeInCeActivity.class)
       .add(10_1_015, "Add 'external_groups' table.", CreateExternalGroupsTable.class)
-      .add(10_1_016, "Add index on 'external_groups(external_identity_provider, external_id).", CreateIndexOnExternalIdAndIdentityOnExternalGroupsTable.class);
+      .add(10_1_016, "Add index on 'external_groups(external_identity_provider, external_id).", CreateIndexOnExternalIdAndIdentityOnExternalGroupsTable.class)
+      .add(10_1_017, "Add 'code_variants' column in 'issues' table", AddCodeVariantsColumnInIssuesTable.class);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTableTest.java
new file mode 100644 (file)
index 0000000..4d98928
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v101;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class AddCodeVariantsColumnInIssuesTableTest {
+
+  private static final String TABLE_NAME = "issues";
+  private static final String COLUMN_NAME = "code_variants";
+  private static final int COLUMN_SIZE = 4000;
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddCodeVariantsColumnInIssuesTableTest.class, "schema.sql");
+
+  private final AddCodeVariantsColumnInIssuesTable underTest = new AddCodeVariantsColumnInIssuesTable(db.database());
+
+  @Test
+  public void migration_should_add_column() throws SQLException {
+    db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+    underTest.execute();
+    db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, VARCHAR, COLUMN_SIZE, true);
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+    underTest.execute();
+    underTest.execute();
+    db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, VARCHAR, COLUMN_SIZE, true);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/AddCodeVariantsColumnInIssuesTableTest/schema.sql
new file mode 100644 (file)
index 0000000..9b5d22f
--- /dev/null
@@ -0,0 +1,37 @@
+CREATE TABLE "ISSUES"(
+    "KEE" CHARACTER VARYING(50) NOT NULL,
+    "RULE_UUID" CHARACTER VARYING(40),
+    "SEVERITY" CHARACTER VARYING(10),
+    "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+    "MESSAGE" CHARACTER VARYING(4000),
+    "LINE" INTEGER,
+    "GAP" DOUBLE PRECISION,
+    "STATUS" CHARACTER VARYING(20),
+    "RESOLUTION" CHARACTER VARYING(20),
+    "CHECKSUM" CHARACTER VARYING(1000),
+    "ASSIGNEE" CHARACTER VARYING(255),
+    "AUTHOR_LOGIN" CHARACTER VARYING(255),
+    "EFFORT" INTEGER,
+    "CREATED_AT" BIGINT,
+    "UPDATED_AT" BIGINT,
+    "ISSUE_CREATION_DATE" BIGINT,
+    "ISSUE_UPDATE_DATE" BIGINT,
+    "ISSUE_CLOSE_DATE" BIGINT,
+    "TAGS" CHARACTER VARYING(4000),
+    "COMPONENT_UUID" CHARACTER VARYING(50),
+    "PROJECT_UUID" CHARACTER VARYING(50),
+    "LOCATIONS" BINARY LARGE OBJECT,
+    "ISSUE_TYPE" TINYINT,
+    "FROM_HOTSPOT" BOOLEAN,
+    "QUICK_FIX_AVAILABLE" BOOLEAN,
+    "RULE_DESCRIPTION_CONTEXT_KEY" CHARACTER VARYING(50),
+    "MESSAGE_FORMATTINGS" BINARY LARGE OBJECT
+);
+ALTER TABLE "ISSUES" ADD CONSTRAINT "PK_ISSUES" PRIMARY KEY("KEE");
+CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES"("ASSIGNEE" NULLS FIRST);
+CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES"("COMPONENT_UUID" NULLS FIRST);
+CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES"("ISSUE_CREATION_DATE" NULLS FIRST);
+CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES"("PROJECT_UUID" NULLS FIRST);
+CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES"("RESOLUTION" NULLS FIRST);
+CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES"("UPDATED_AT" NULLS FIRST);
+CREATE INDEX "ISSUES_RULE_UUID" ON "ISSUES"("RULE_UUID" NULLS FIRST);
index f310c527e3979c0e4e652efc0155a875fe8f229f..b66bfe94f3107d742b6ac2215ced2f547c61d300 100644 (file)
@@ -77,6 +77,7 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
   private String authorLogin = null;
   private List<DefaultIssueComment> comments = null;
   private Set<String> tags = null;
+  private Set<String> codeVariants = null;
   // temporarily an Object as long as DefaultIssue is used by sonar-batch
   private Object locations = null;
 
@@ -653,7 +654,7 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
   @Override
   public Set<String> tags() {
     if (tags == null) {
-      return ImmutableSet.of();
+      return Set.of();
     } else {
       return ImmutableSet.copyOf(tags);
     }
@@ -664,6 +665,20 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
     return this;
   }
 
+  @Override
+  public Set<String> codeVariants() {
+    if (codeVariants == null) {
+      return Set.of();
+    } else {
+      return ImmutableSet.copyOf(codeVariants);
+    }
+  }
+
+  public DefaultIssue setCodeVariants(Collection<String> codeVariants) {
+    this.codeVariants = new LinkedHashSet<>(codeVariants);
+    return this;
+  }
+
   public Optional<String> getRuleDescriptionContextKey() {
     return Optional.ofNullable(ruleDescriptionContextKey);
   }
index 3fdc6daa25e435e0ee893f915f1125ef7b0dac46..e51e8b13e3d514a331171cba3cf6cf1682f06685 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.core.issue;
 
 import java.text.SimpleDateFormat;
 import java.util.List;
+import java.util.Set;
 import org.apache.commons.lang.StringUtils;
 import org.junit.Test;
 import org.sonar.api.issue.Issue;
@@ -29,7 +30,6 @@ import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.Duration;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -73,7 +73,9 @@ public class DefaultIssueTest {
       .setCloseDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-21"))
       .setSelectedAt(1400000000000L)
       .setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
-      .setType(RuleType.BUG);
+      .setType(RuleType.BUG)
+      .setTags(Set.of("tag1", "tag2"))
+      .setCodeVariants(Set.of("variant1", "variant2"));
 
     assertThat((Object) issue.getLocations()).isEqualTo("loc");
     assertThat(issue.locationsChanged()).isTrue();
@@ -109,6 +111,8 @@ public class DefaultIssueTest {
     assertThat(issue.selectedAt()).isEqualTo(1400000000000L);
     assertThat(issue.getRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
     assertThat(issue.type()).isEqualTo(RuleType.BUG);
+    assertThat(issue.tags()).containsOnly("tag1", "tag2");
+    assertThat(issue.codeVariants()).containsOnly("variant1", "variant2");
   }
 
   @Test
index d66b6c14f8f4b06f69f231806c96e7018edc6af3..6a585ae377914d8ca705a3a7b7d85f1c88617e8a 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.api.batch.sensor.issue.internal;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import javax.annotation.Nullable;
@@ -43,6 +44,7 @@ public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements
   private Severity overriddenSeverity;
   private boolean quickFixAvailable = false;
   private String ruleDescriptionContextKey;
+  private List<String> codeVariants;
 
   public DefaultIssue(DefaultInputProject project) {
     this(project, null);
@@ -97,6 +99,16 @@ public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements
     return this;
   }
 
+  @Override
+  public DefaultIssue setCodeVariants(@Nullable Iterable<String> codeVariants) {
+    if (codeVariants != null) {
+      List<String> codeVariantsList = new ArrayList<>();
+      codeVariants.forEach(codeVariantsList::add);
+      this.codeVariants = codeVariantsList;
+    }
+    return this;
+  }
+
   @Override
   public boolean isQuickFixAvailable() {
     return quickFixAvailable;
@@ -112,6 +124,11 @@ public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements
     throw new UnsupportedOperationException();
   }
 
+  @Override
+  public List<String> codeVariants() {
+    return codeVariants;
+  }
+
   @Override
   public Severity overriddenSeverity() {
     return this.overriddenSeverity;
index eb8da729e85d023e2b375ce9014b3aa896d566f8..f35583825ca64112f6e7d3235cb4cf0a6eb95bd7 100644 (file)
@@ -79,7 +79,8 @@ public class DefaultIssueTest {
         .message("Wrong way!"))
       .forRule(RULE_KEY)
       .gap(10.0)
-      .setRuleDescriptionContextKey("spring");
+      .setRuleDescriptionContextKey("spring")
+      .setCodeVariants(List.of("variant1", "variant2"));
 
     assertThat(issue.primaryLocation().inputComponent()).isEqualTo(inputFile);
     assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
@@ -87,6 +88,7 @@ public class DefaultIssueTest {
     assertThat(issue.gap()).isEqualTo(10.0);
     assertThat(issue.primaryLocation().message()).isEqualTo("Wrong way!");
     assertThat(issue.ruleDescriptionContextKey()).contains("spring");
+    assertThat(issue.codeVariants()).containsOnly("variant1", "variant2");
 
     issue.save();