Browse Source

SONAR-20021 Add impacts support in IssueDto, RuleDto

tags/10.2.0.77647
Jacek Poreda 10 months ago
parent
commit
d9cedb7e0e
23 changed files with 640 additions and 60 deletions
  1. 2
    0
      server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
  2. 63
    4
      server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
  3. 20
    14
      server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java
  4. 6
    3
      server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java
  5. 1
    0
      server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java
  6. 60
    16
      server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java
  7. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
  8. 83
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java
  9. 13
    3
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
  10. 58
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
  11. 4
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
  12. 5
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
  13. 5
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
  14. 4
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
  15. 12
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java
  16. 30
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
  17. 5
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java
  18. 40
    9
      server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
  19. 20
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
  20. 49
    3
      server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml
  21. 90
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
  22. 61
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java
  23. 7
    3
      server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java

+ 2
- 0
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java View File

@@ -58,6 +58,7 @@ public final class SqTables {
"internal_component_props",
"internal_properties",
"issues",
"issues_impacts",
"issue_changes",
"live_measures",
"metrics",
@@ -96,6 +97,7 @@ public final class SqTables {
"report_subscriptions",
"rules",
"rule_desc_sections",
"rules_default_impacts",
"rules_parameters",
"rules_profiles",
"rule_repositories",

+ 63
- 4
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java View File

@@ -31,9 +31,12 @@ import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.Pagination;
@@ -101,6 +104,8 @@ public class IssueDaoIT {

@Before
public void setup() {
int i = db.countSql(db.getSession(), "select count(1) from rules_default_impacts");

db.rules().insert(RULE.setIsExternal(true));
projectDto = db.components().insertPrivateProject(t -> t.setUuid(PROJECT_UUID).setKey(PROJECT_KEY)).getMainBranchComponent();
db.components().insertComponent(newFileDto(projectDto).setUuid(FILE_UUID).setKey(FILE_KEY));
@@ -142,7 +147,7 @@ public class IssueDaoIT {
IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);

assertThat(issue).usingRecursiveComparison()
.ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute")
.ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute", "impacts", "ruleDefaultImpacts")
.isEqualTo(expected);
assertThat(issue.parseMessageFormattings()).isEqualTo(MESSAGE_FORMATTING);
assertThat(issue.getIssueCreationDate()).isNotNull();
@@ -152,6 +157,18 @@ public class IssueDaoIT {
assertThat(issue.getRule()).isEqualTo(RULE.getRuleKey());
assertThat(issue.getCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute());
assertThat(issue.parseLocations()).isNull();
assertThat(issue.getImpacts())
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
.containsExactlyInAnyOrder(
tuple(Severity.MEDIUM, SoftwareQuality.RELIABILITY),
tuple(Severity.LOW, SoftwareQuality.SECURITY));

assertThat(issue.getEffectiveImpacts())
// impacts from rule
.containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
// impacts from issue
.containsEntry(SoftwareQuality.RELIABILITY, Severity.MEDIUM)
.containsEntry(SoftwareQuality.SECURITY, Severity.LOW);
}

@Test
@@ -169,8 +186,15 @@ public class IssueDaoIT {
prepareTables();

List<IssueDto> issues = underTest.selectByKeys(db.getSession(), asList("I1", "I2", "I3"));
// results are not ordered, so do not use "containsExactly"
assertThat(issues).extracting("key").containsOnly("I1", "I2");

assertThat(issues).extracting(IssueDto::getKey).containsExactlyInAnyOrder("I1", "I2");
assertThat(issues).filteredOn(issueDto -> issueDto.getKey().equals("I1"))
.extracting(IssueDto::getImpacts)
.flatMap(issueImpactDtos -> issueImpactDtos)
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
.containsExactlyInAnyOrder(
tuple(Severity.MEDIUM, SoftwareQuality.RELIABILITY),
tuple(Severity.LOW, SoftwareQuality.SECURITY));
}

@Test
@@ -537,6 +561,32 @@ public class IssueDaoIT {
assertThat(issue1.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
}

@Test
public void insert_shouldInsertBatchIssuesWithImpacts() {
ImpactDto impact1 = new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(SoftwareQuality.MAINTAINABILITY)
.setSeverity(Severity.HIGH);
ImpactDto impact2 = new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(SoftwareQuality.SECURITY)
.setSeverity(Severity.LOW);
IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
.addImpact(impact1)
.addImpact(impact2);
IssueDto issue2 = createIssueWithKey(ISSUE_KEY2);
underTest.insert(db.getSession(), issue1, issue2);

List<IssueDto> issueDtos = underTest.selectByKeys(db.getSession(), Set.of(ISSUE_KEY1, ISSUE_KEY2));
assertThat(issueDtos)
.extracting(IssueDto::getKey)
.containsExactlyInAnyOrder(ISSUE_KEY1, ISSUE_KEY2);
assertThat(issueDtos).filteredOn(issueDto -> issueDto.getKey().equals(ISSUE_KEY1))
.flatExtracting(IssueDto::getImpacts)
.containsExactlyInAnyOrder(impact1, impact2)
.doesNotContainNull();
}

@Test
public void update_whenUpdatingRuleDescriptionContextKeyToNull_returnsEmptyContextKey() {
IssueDto issue = createIssueWithKey(ISSUE_KEY1).setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
@@ -783,7 +833,9 @@ public class IssueDaoIT {
.setMessage("the message")
.setRuleUuid(RULE.getUuid())
.setComponentUuid(FILE_UUID)
.setProjectUuid(PROJECT_UUID));
.setProjectUuid(PROJECT_UUID)
.addImpact(newIssueImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM))
.addImpact(newIssueImpact(SoftwareQuality.SECURITY, Severity.LOW)));
underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
.setRuleUuid(RULE.getUuid())
.setComponentUuid(FILE_UUID)
@@ -792,6 +844,13 @@ public class IssueDaoIT {
db.getSession().commit();
}

private static ImpactDto newIssueImpact(SoftwareQuality softwareQuality, Severity severity) {
return new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(softwareQuality)
.setSeverity(severity);
}

private static RuleType randomRuleTypeExceptHotspot() {
return RULE_TYPES_EXCEPT_HOTSPOT[nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)];
}

+ 20
- 14
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java View File

@@ -38,6 +38,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
@@ -424,13 +426,10 @@ public class IssueMapperIT {
underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);

assertThat(resultHandler.issues)
.hasSize(4)
.hasSize(1)
.extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
.containsExactly(
tuple(issue.getKey(), changes[2].getChangeData()),
tuple(issue.getKey(), changes[3].getChangeData()),
tuple(issue.getKey(), changes[0].getChangeData()),
tuple(issue.getKey(), changes[1].getChangeData()));
// we are interested only in the latest closed issue change data
.containsExactly(tuple(issue.getKey(), changes[2].getChangeData()));
}

@Test
@@ -450,12 +449,11 @@ public class IssueMapperIT {
underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);

assertThat(resultHandler.issues)
.hasSize(3)
.hasSize(1)
.extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
.containsExactly(
tuple(issue.getKey(), changes[2].getChangeData()),
tuple(issue.getKey(), changes[3].getChangeData()),
tuple(issue.getKey(), changes[1].getChangeData()));
// we are interested only in the latest closed issue change data
tuple(issue.getKey(), changes[2].getChangeData()));
}

private IssueChangeDto insertToClosedDiff(IssueDto issueDto) {
@@ -484,7 +482,7 @@ public class IssueMapperIT {
}

@SafeVarargs
private final IssueDto insertNewClosedIssue(ComponentDto component, RuleType ruleType, Consumer<IssueDto>... consumers) {
private IssueDto insertNewClosedIssue(ComponentDto component, RuleType ruleType, Consumer<IssueDto>... consumers) {
RuleDto rule = dbTester.rules().insert(t -> t.setType(ruleType));
return insertNewClosedIssue(component, rule, system2.now(), consumers);
}
@@ -495,7 +493,7 @@ public class IssueMapperIT {
}

@SafeVarargs
private final IssueDto insertNewClosedIssue(ComponentDto component, RuleDto rule, long issueCloseTime, Consumer<IssueDto>... consumers) {
private IssueDto insertNewClosedIssue(ComponentDto component, RuleDto rule, long issueCloseTime, Consumer<IssueDto>... consumers) {
IssueDto res = new IssueDto()
.setKee(UuidFactoryFast.getInstance().create())
.setRuleUuid(rule.getUuid())
@@ -503,9 +501,17 @@ public class IssueMapperIT {
.setComponentUuid(component.uuid())
.setProjectUuid(component.branchUuid())
.setStatus(Issue.STATUS_CLOSED)
.setIssueCloseTime(issueCloseTime);
.setIssueCloseTime(issueCloseTime)
.addImpact(new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSeverity(Severity.HIGH)
.setSoftwareQuality(SoftwareQuality.MAINTAINABILITY))
.addImpact(new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSeverity(Severity.LOW)
.setSoftwareQuality(SoftwareQuality.SECURITY));
Arrays.asList(consumers).forEach(c -> c.accept(res));
underTest.insert(res);
dbTester.getDbClient().issueDao().insert(dbSession, res);
dbSession.commit();
return res;
}

+ 6
- 3
server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java View File

@@ -37,6 +37,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbTester;
@@ -50,6 +52,7 @@ import org.sonar.db.dialect.Dialect;
import org.sonar.db.duplication.DuplicationUnitDto;
import org.sonar.db.entity.EntityDto;
import org.sonar.db.issue.AnticipatedTransitionDto;
import org.sonar.db.issue.ImpactDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.newcodeperiod.NewCodePeriodType;
@@ -565,7 +568,7 @@ public class PurgeCommandsIT {
underTest.deleteIssues(projectOrView.uuid());

assertThat(dbTester.countSql("select count(uuid) from new_code_reference_issues where issue_key in (" +
String.join(", ", issueKeys) + ")")).isZero();
String.join(", ", issueKeys) + ")")).isZero();
}

@Test
@@ -957,14 +960,14 @@ public class PurgeCommandsIT {

@DataProvider
public static Object[] projects() {
return new Object[]{
return new Object[] {
ComponentTesting.newPrivateProjectDto(), ComponentTesting.newPublicProjectDto(),
};
}

@DataProvider
public static Object[] views() {
return new Object[]{
return new Object[] {
ComponentTesting.newPortfolio(), ComponentTesting.newApplication()
};
}

+ 1
- 0
server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java View File

@@ -1226,6 +1226,7 @@ public class PurgeDaoIT {

assertThat(db.countRowsOfTable("issue_changes")).isZero();
assertThat(db.countRowsOfTable("new_code_reference_issues")).isZero();
assertThat(db.countRowsOfTable("issues_impacts")).isZero();
assertThat(db.select("select kee as \"KEE\" from issues")).extracting(i -> i.get("KEE")).containsOnly(issue1.getKey());
}


+ 60
- 16
server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java View File

@@ -29,11 +29,13 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.ibatis.exceptions.PersistenceException;
import org.jetbrains.annotations.NotNull;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
@@ -44,8 +46,10 @@ import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.RowNotFoundException;
import org.sonar.db.issue.ImpactDto;
import org.sonar.db.rule.RuleDto.Scope;

import static com.google.common.collect.Sets.newHashSet;
@@ -56,6 +60,9 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
import static org.sonar.api.rule.RuleStatus.REMOVED;

public class RuleDaoIT {
@@ -68,12 +75,18 @@ public class RuleDaoIT {

@Test
public void selectByKey() {
RuleDto ruleDto = db.rules().insert();
RuleDto ruleDto = db.rules().insert(r -> r.addDefaultImpact(newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.LOW)));

assertThat(underTest.selectByKey(db.getSession(), RuleKey.of("foo", "bar")))
.isEmpty();
RuleDto actualRule = underTest.selectByKey(db.getSession(), ruleDto.getKey()).get();
assertEquals(actualRule, ruleDto);

assertThat(actualRule.getDefaultImpacts())
.extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
.containsExactlyInAnyOrder(
tuple(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH),
tuple(SECURITY, org.sonar.api.issue.impact.Severity.LOW));
}

@Test
@@ -83,7 +96,6 @@ public class RuleDaoIT {
assertThat(underTest.selectByKey(db.getSession(), ruleDto.getKey())).isNotEmpty();
}


@Test
public void selectByUuid() {
RuleDto ruleDto = db.rules().insert();
@@ -153,14 +165,9 @@ public class RuleDaoIT {

@Test
public void selectOrFailByKey_fails_if_rule_not_found() {
assertThatThrownBy(() -> underTest.selectOrFailByKey(db.getSession(), RuleKey.of("NOT", "FOUND")))
.isInstanceOf(RowNotFoundException.class)
.hasMessage("Rule with key 'NOT:FOUND' does not exist");
}

@Test
public void selectOrFailDefinitionByKey_fails_if_rule_not_found() {
assertThatThrownBy(() -> underTest.selectOrFailByKey(db.getSession(), RuleKey.of("NOT", "FOUND")))
DbSession session = db.getSession();
RuleKey ruleKey = RuleKey.of("NOT", "FOUND");
assertThatThrownBy(() -> underTest.selectOrFailByKey(session, ruleKey))
.isInstanceOf(RowNotFoundException.class)
.hasMessage("Rule with key 'NOT:FOUND' does not exist");
}
@@ -192,13 +199,40 @@ public class RuleDaoIT {

@Test
public void selectAll() {
RuleDto rule1 = db.rules().insertRule();
RuleDto rule2 = db.rules().insertRule();
RuleDto rule1 = db.rules().insertRule(r -> r.addDefaultImpact(newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.LOW)));
RuleDto rule2 = db.rules().insertRule(r -> r.addDefaultImpact(newRuleDefaultImpact(RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)));
RuleDto rule3 = db.rules().insertRule();

assertThat(underTest.selectAll(db.getSession()))
List<RuleDto> ruleDtos = underTest.selectAll(db.getSession());
assertThat(ruleDtos)
.extracting(RuleDto::getUuid)
.containsOnly(rule1.getUuid(), rule2.getUuid(), rule3.getUuid());
.containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(), rule3.getUuid());

assertThat(ruleDtos)
.filteredOn(ruleDto -> ruleDto.getUuid().equals(rule1.getUuid()))
.extracting(RuleDto::getDefaultImpacts)
.flatMap(Function.identity())
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
.containsExactlyInAnyOrder(
tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY),
tuple(org.sonar.api.issue.impact.Severity.LOW, SECURITY));

assertThat(ruleDtos)
.filteredOn(ruleDto -> ruleDto.getUuid().equals(rule2.getUuid()))
.extracting(RuleDto::getDefaultImpacts)
.flatMap(Function.identity())
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
.containsExactlyInAnyOrder(
tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY),
tuple(org.sonar.api.issue.impact.Severity.MEDIUM, RELIABILITY));

assertThat(ruleDtos)
.filteredOn(ruleDto -> ruleDto.getUuid().equals(rule3.getUuid()))
.extracting(RuleDto::getDefaultImpacts)
.flatMap(Function.identity())
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
.containsExactlyInAnyOrder(
tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY));
}

private void assertEquals(RuleDto actual, RuleDto expected) {
@@ -313,8 +347,7 @@ public class RuleDaoIT {
.extracting(RuleDto::getUuid, RuleDto::getLanguage, RuleDto::getType)
.containsExactlyInAnyOrder(
tuple(rule1.getUuid(), "java", RuleType.VULNERABILITY.getDbConstant()),
tuple(rule3.getUuid(), "java", RuleType.BUG.getDbConstant())
);
tuple(rule3.getUuid(), "java", RuleType.BUG.getDbConstant()));

assertThat(underTest.selectByLanguage(db.getSession(), "js")).hasSize(1);

@@ -436,6 +469,7 @@ public class RuleDaoIT {
@Test
public void insert() {
RuleDescriptionSectionDto sectionDto = createDefaultRuleDescriptionSection();
ImpactDto ruleDefaultImpactDto = newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.HIGH);
RuleDto newRule = new RuleDto()
.setUuid("rule-uuid")
.setRuleKey("NewRuleKey")
@@ -443,6 +477,7 @@ public class RuleDaoIT {
.setName("new name")
.setDescriptionFormat(RuleDto.Format.MARKDOWN)
.addRuleDescriptionSectionDto(sectionDto)
.addDefaultImpact(ruleDefaultImpactDto)
.setStatus(RuleStatus.DEPRECATED)
.setConfigKey("NewConfigKey")
.setSeverity(Severity.INFO)
@@ -492,6 +527,8 @@ public class RuleDaoIT {
assertThat(ruleDto.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator()
.containsOnly(sectionDto);
assertThat(ruleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
assertThat(ruleDto.getDefaultImpacts()).usingRecursiveFieldByFieldElementComparator()
.containsOnly(ruleDefaultImpactDto);
}

@Test
@@ -1148,6 +1185,13 @@ public class RuleDaoIT {
.isInstanceOf(PersistenceException.class);
}

private static ImpactDto newRuleDefaultImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) {
return new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(softwareQuality)
.setSeverity(severity);
}

private static RuleDescriptionSectionDto createDefaultRuleDescriptionSection() {
return RuleDescriptionSectionDto.createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), RandomStringUtils.randomAlphanumeric(1000));
}

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java View File

@@ -72,6 +72,7 @@ import org.sonar.db.event.EventDto;
import org.sonar.db.event.EventMapper;
import org.sonar.db.issue.AnticipatedTransitionDto;
import org.sonar.db.issue.AnticipatedTransitionMapper;
import org.sonar.db.issue.ImpactDto;
import org.sonar.db.issue.IssueChangeDto;
import org.sonar.db.issue.IssueChangeMapper;
import org.sonar.db.issue.IssueDto;
@@ -212,6 +213,7 @@ public class MyBatis {
confBuilder.loadAlias("InternalComponentProperty", InternalComponentPropertyDto.class);
confBuilder.loadAlias("IssueChange", IssueChangeDto.class);
confBuilder.loadAlias("KeyLongValue", KeyLongValue.class);
confBuilder.loadAlias("Impact", ImpactDto.class);
confBuilder.loadAlias("Issue", IssueDto.class);
confBuilder.loadAlias("NewCodeReferenceIssue", NewCodeReferenceIssueDto.class);
confBuilder.loadAlias("Measure", MeasureDto.class);

+ 83
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java View File

@@ -0,0 +1,83 @@
/*
* 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.db.issue;

import java.io.Serializable;
import java.util.Objects;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;

public class ImpactDto implements Serializable {
private String uuid;
private SoftwareQuality softwareQuality;
private Severity severity;

public ImpactDto() {
// nothing to do
}

public String getUuid() {
return uuid;
}

public ImpactDto setUuid(String uuid) {
this.uuid = uuid;
return this;
}

public SoftwareQuality getSoftwareQuality() {
return softwareQuality;
}

public ImpactDto setSoftwareQuality(SoftwareQuality softwareQuality) {
this.softwareQuality = softwareQuality;
return this;
}

public Severity getSeverity() {
return severity;
}

public ImpactDto setSeverity(Severity severity) {
this.severity = severity;
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (o == null || getClass() != o.getClass()) {
return false;
}

ImpactDto impactDto = (ImpactDto) o;
return Objects.equals(uuid, impactDto.uuid)
&& Objects.equals(softwareQuality, impactDto.softwareQuality)
&& Objects.equals(severity, impactDto.severity);
}

@Override
public int hashCode() {
return Objects.hash(uuid, softwareQuality, severity);
}
}

+ 13
- 3
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java View File

@@ -93,13 +93,23 @@ public class IssueDao implements Dao {

public void insert(DbSession session, IssueDto dto) {
mapper(session).insert(dto);
updateIssueImpacts(dto, mapper(session));
}

private static void updateIssueImpacts(IssueDto issueDto, IssueMapper mapper) {
mapper.deleteIssueImpacts(issueDto.getKey());
insertInsertIssueImpacts(issueDto, mapper);
}

private static void insertInsertIssueImpacts(IssueDto issueDto, IssueMapper mapper) {
issueDto.getImpacts()
.forEach(impact -> mapper.insertIssueImpact(issueDto.getKey(), impact));
}

public void insert(DbSession session, IssueDto dto, IssueDto... others) {
IssueMapper mapper = mapper(session);
mapper.insert(dto);
insert(session, dto);
for (IssueDto other : others) {
mapper.insert(other);
insert(session, other);
}
}


+ 58
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java View File

@@ -26,13 +26,20 @@ import com.google.common.collect.ImmutableSet;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
@@ -43,6 +50,7 @@ import org.sonar.db.protobuf.DbIssues;
import org.sonar.db.rule.RuleDto;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static org.sonar.api.utils.DateUtils.dateToLong;
import static org.sonar.api.utils.DateUtils.longToDate;

@@ -103,6 +111,9 @@ public final class IssueDto implements Serializable {
// populate only when retrieving closed issue for issue tracking
private String closedChangeData;

private Set<ImpactDto> impacts = new HashSet<>();
private Set<ImpactDto> ruleDefaultImpacts = new HashSet<>();

public IssueDto() {
// nothing to do
}
@@ -293,7 +304,7 @@ public final class IssueDto implements Serializable {
try {
return DbIssues.MessageFormattings.parseFrom(messageFormattings);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(String.format("Fail to read ISSUES.MESSAGE_FORMATTINGS [KEE=%s]", kee), e);
throw new IllegalStateException(format("Fail to read ISSUES.MESSAGE_FORMATTINGS [KEE=%s]", kee), e);
}
}
return null;
@@ -695,7 +706,7 @@ public final class IssueDto implements Serializable {
try {
return DbIssues.Locations.parseFrom(locations);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(String.format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e);
throw new IllegalStateException(format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e);
}
}
return null;
@@ -769,6 +780,51 @@ public final class IssueDto implements Serializable {
return this;
}

/**
* Return impacts defined on this issue.
*
* @return Collection of impacts
*/
public Set<ImpactDto> getImpacts() {
return impacts;
}

public IssueDto addImpact(ImpactDto impact) {
impacts.stream().filter(impactDto -> impactDto.getSoftwareQuality() == impact.getSoftwareQuality()).findFirst()
.ifPresent(impactDto -> {
throw new IllegalStateException(format("Impact already defined on issue for Software Quality [%s]", impact.getSoftwareQuality()));
});

impacts.add(impact);
return this;
}

public IssueDto replaceAllImpacts(Collection<ImpactDto> newImpacts) {
Set<SoftwareQuality> newSoftwareQuality = newImpacts.stream().map(ImpactDto::getSoftwareQuality).collect(Collectors.toSet());
if (newSoftwareQuality.size() != newImpacts.size()) {
throw new IllegalStateException("Impacts must have unique Software Quality values");
}
impacts.clear();
impacts.addAll(newImpacts);
return this;
}

Set<ImpactDto> getRuleDefaultImpacts() {
return ruleDefaultImpacts;
}

/**
* Returns effective impacts defined on this issue along with default ones.
*
* @return Unmodifiable Map of impacts
*/
public Map<SoftwareQuality, Severity> getEffectiveImpacts() {
EnumMap<SoftwareQuality, Severity> effectiveImpacts = new EnumMap<>(SoftwareQuality.class);
ruleDefaultImpacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));
impacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));
return Collections.unmodifiableMap(effectiveImpacts);
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

+ 4
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java View File

@@ -62,6 +62,8 @@ public interface IssueMapper {

void insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto issue);

void insertIssueImpact(@Param("issueKey") String issueKey, @Param("dto") ImpactDto issue);

void deleteAsNewCodeOnReferenceBranch(String issueKey);

int updateIfBeforeSelectedDate(IssueDto issue);
@@ -74,8 +76,9 @@ public interface IssueMapper {

List<IssueDto> selectByBranch(@Param("keys") Set<String> keys, @Nullable @Param("changedSince") Long changedSince);


List<String> selectRecentlyClosedIssues(@Param("queryParams") IssueQueryParams issueQueryParams);

List<String> selectIssueKeysByQuery(@Param("query") IssueListQuery issueListQuery, @Param("pagination") Pagination pagination);

void deleteIssueImpacts(String issueKey);
}

+ 5
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java View File

@@ -198,6 +198,11 @@ class PurgeCommands {
session.commit();
profiler.stop();

profiler.start("deleteIssues (issues_impacts)");
purgeMapper.deleteIssuesImpactsByProjectUuid(rootUuid);
session.commit();
profiler.stop();

profiler.start("deleteIssues (issues)");
purgeMapper.deleteIssuesByProjectUuid(rootUuid);
session.commit();

+ 5
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java View File

@@ -139,6 +139,11 @@ public class PurgeDao implements Dao {
return emptyList();
});

executeLargeInputs(issueKeys, input -> {
mapper.deleteIssuesImpactsFromKeys(input);
return emptyList();
});

executeLargeInputs(issueKeys, input -> {
mapper.deleteIssuesFromKeys(input);
return emptyList();

+ 4
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java View File

@@ -92,6 +92,8 @@ public interface PurgeMapper {

void deleteNewCodeReferenceIssuesByProjectUuid(@Param("projectUuid") String projectUuid);

void deleteIssuesImpactsByProjectUuid(@Param("projectUuid") String projectUuid);

List<String> selectOldClosedIssueKeys(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate);

List<String> selectStaleBranchesAndPullRequests(@Param("projectUuid") String projectUuid, @Param("toDate") Long toDate);
@@ -107,6 +109,8 @@ public interface PurgeMapper {

void deleteNewCodeReferenceIssuesFromKeys(@Param("issueKeys") List<String> keys);

void deleteIssuesImpactsFromKeys(@Param("issueKeys") List<String> keys);

void deleteFileSourcesByProjectUuid(String rootProjectUuid);

void deleteFileSourcesByFileUuid(@Param("fileUuids") List<String> fileUuids);

+ 12
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java View File

@@ -102,12 +102,14 @@ public class RuleDao implements Dao {
RuleMapper mapper = mapper(session);
mapper.insertRule(ruleDto);
updateRuleDescriptionSectionDtos(ruleDto, mapper);
updateRuleDefaultImpacts(ruleDto, mapper);
}

public void update(DbSession session, RuleDto ruleDto) {
RuleMapper mapper = mapper(session);
mapper.updateRule(ruleDto);
updateRuleDescriptionSectionDtos(ruleDto, mapper);
updateRuleDefaultImpacts(ruleDto, mapper);
}

private static void updateRuleDescriptionSectionDtos(RuleDto ruleDto, RuleMapper mapper) {
@@ -120,6 +122,16 @@ public class RuleDao implements Dao {
.forEach(section -> mapper.insertRuleDescriptionSection(ruleDto.getUuid(), section));
}

private static void updateRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) {
mapper.deleteRuleDefaultImpacts(ruleDto.getUuid());
insertRuleDefaultImpacts(ruleDto, mapper);
}

private static void insertRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) {
ruleDto.getDefaultImpacts()
.forEach(section -> mapper.insertRuleDefaultImpact(ruleDto.getUuid(), section));
}

public void scrollIndexingRuleExtensionsByIds(DbSession dbSession, Collection<String> ruleExtensionIds, Consumer<RuleExtensionForIndexingDto> consumer) {
RuleMapper mapper = mapper(dbSession);


+ 30
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java View File

@@ -27,16 +27,20 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.db.issue.ImpactDto;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
@@ -68,6 +72,9 @@ public class RuleDto {
*/
private RuleDto.Format descriptionFormat = null;
private RuleStatus status = null;

private Set<ImpactDto> defaultImpacts = new HashSet<>();

private String name = null;
private String configKey = null;

@@ -267,6 +274,29 @@ public class RuleDto {
return this;
}

public Set<ImpactDto> getDefaultImpacts() {
return defaultImpacts;
}

public RuleDto addDefaultImpact(ImpactDto defaultImpactDto) {
defaultImpacts.stream().filter(impactDto -> impactDto.getSoftwareQuality() == defaultImpactDto.getSoftwareQuality()).findFirst()
.ifPresent(impactDto -> {
throw new IllegalStateException(format("Impact already defined on rule for Software Quality [%s]", defaultImpactDto.getSoftwareQuality()));
});
defaultImpacts.add(defaultImpactDto);
return this;
}

public RuleDto replaceAllDefaultImpacts(Collection<ImpactDto> newImpacts) {
Set<SoftwareQuality> newSoftwareQuality = newImpacts.stream().map(ImpactDto::getSoftwareQuality).collect(Collectors.toSet());
if (newSoftwareQuality.size() != newImpacts.size()) {
throw new IllegalStateException("Impacts must have unique Software Quality values");
}
defaultImpacts.clear();
defaultImpacts.addAll(newImpacts);
return this;
}

public String getName() {
return name;
}
@@ -424,7 +454,6 @@ public class RuleDto {
return this;
}


@CheckForNull
public String getDefRemediationFunction() {
return defRemediationFunction;

+ 5
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java View File

@@ -25,6 +25,7 @@ import java.util.Set;
import org.apache.ibatis.annotations.Param;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleQuery;
import org.sonar.db.issue.ImpactDto;

public interface RuleMapper {

@@ -52,6 +53,8 @@ public interface RuleMapper {

void insertRuleDescriptionSection(@Param("ruleUuid") String ruleUuid, @Param("dto") RuleDescriptionSectionDto ruleDescriptionSectionDto);

void insertRuleDefaultImpact(@Param("ruleUuid") String ruleUuid, @Param("dto") ImpactDto ruleDefaultImpactDto);

void updateRule(RuleDto ruleDefinitionDto);

void deleteRuleDescriptionSection(String ruleUuid);
@@ -77,4 +80,6 @@ public interface RuleMapper {
void deleteDeprecatedRuleKeys(@Param("uuids") List<String> uuids);

void insertDeprecatedRuleKey(DeprecatedRuleKeyDto deprecatedRuleKeyDto);

void deleteRuleDefaultImpacts(String ruleUuid);
}

+ 40
- 9
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml View File

@@ -41,6 +41,8 @@
i.issue_type as type,
i.quick_fix_available as quickFixAvailable,
i.code_variants as codeVariantsString,
<include refid="issueImpactsColumns"/>
<include refid="ruleDefaultImpactsColumns"/>
<include refid="isNewCodeReferenceIssue"/>
</sql>

@@ -109,6 +111,34 @@
</if>
</sql>

<sql id="issueImpactsColumns">
ii.uuid as "ii_uuid",
ii.software_quality as "ii_softwareQuality",
ii.severity as "ii_severity",
</sql>
<sql id="ruleDefaultImpactsColumns">
rdi.uuid as "rdi_uuid",
rdi.software_quality as "rdi_softwareQuality",
rdi.severity as "rdi_severity",
</sql>

<resultMap id="issueResultMap" type="Issue" autoMapping="true">
<id property="kee" column="kee"/>

<collection property="impacts" column="ii_uuid" notNullColumn="ii_uuid"
javaType="java.util.Set" ofType="Impact">
<id property="uuid" column="ii_uuid"/>
<result property="softwareQuality" column="ii_softwareQuality"/>
<result property="severity" column="ii_severity"/>
</collection>
<collection property="ruleDefaultImpacts" column="rdi_uuid" notNullColumn="rdi_uuid"
javaType="java.util.Set" ofType="Impact">
<id property="uuid" column="rdi_uuid"/>
<result property="softwareQuality" column="rdi_softwareQuality"/>
<result property="severity" column="rdi_severity"/>
</collection>
</resultMap>

<insert id="insert" parameterType="Issue" useGeneratedKeys="false">
INSERT INTO issues (kee, rule_uuid, severity, manual_severity,
message, message_formattings, line, locations, gap, effort, status, tags, rule_description_context_key,
@@ -225,7 +255,7 @@
where kee = #{kee} and updated_at &lt;= #{selectedAt}
</update>

<select id="selectByKey" parameterType="String" resultType="Issue">
<select id="selectByKey" parameterType="String" resultMap="issueResultMap">
select
<include refid="issueColumns"/>,
u.login as assigneeLogin
@@ -240,8 +270,7 @@
where i.kee=#{kee,jdbcType=VARCHAR}
</select>

<select id="scrollNonClosedByComponentUuid" parameterType="String" resultType="Issue" fetchSize="${_scrollFetchSize}"
resultSetType="FORWARD_ONLY">
<select id="scrollNonClosedByComponentUuid" parameterType="String" resultMap="issueResultMap" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY" resultOrdered="true">
select
<include refid="issueColumns"/>
from issues i
@@ -254,9 +283,11 @@
where
i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and
i.status &lt;&gt; 'CLOSED'
order by
i.kee
</select>

<select id="scrollClosedByComponentUuid" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
<select id="scrollClosedByComponentUuid" resultMap="issueResultMap" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY" resultOrdered="true">
select
<include refid="issueColumns"/>,
ic.change_data as closedChangeData
@@ -292,7 +323,7 @@
where i.project_uuid=#{projectUuid,jdbcType=VARCHAR} and i.status &lt;&gt; 'CLOSED'
</select>

<select id="selectByKeys" parameterType="map" resultType="Issue">
<select id="selectByKeys" parameterType="map" resultMap="issueResultMap">
select
<include refid="issueColumns"/>
from issues i
@@ -308,7 +339,7 @@
</foreach>
</select>

<select id="selectByKeysIfNotUpdatedAt" parameterType="map" resultType="Issue">
<select id="selectByKeysIfNotUpdatedAt" parameterType="map" resultMap="issueResultMap">
select
<include refid="issueColumns"/>
from issues i
@@ -617,7 +648,7 @@
order by i.rn asc
</select>

<select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue">
<select id="selectByComponentUuidPaginated" parameterType="map" resultMap="issueResultMap">
select
<include refid="issueColumns"/>,
u.login as assigneeLogin
@@ -634,7 +665,7 @@
limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER}
</select>

<select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="mssql">
<select id="selectByComponentUuidPaginated" parameterType="map" resultMap="issueResultMap" databaseId="mssql">
select
<include refid="issueColumns"/>,
u.login as assigneeLogin
@@ -656,7 +687,7 @@
left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
</select>

<select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="oracle">
<select id="selectByComponentUuidPaginated" parameterType="map" resultMap="issueResultMap" databaseId="oracle">
select
<include refid="issueColumns"/>,
u.login as assigneeLogin

+ 20
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml View File

@@ -291,6 +291,18 @@
)
</delete>

<delete id="deleteIssuesImpactsByProjectUuid" parameterType="map">
delete from issues_impacts
where
issue_key in (
select
kee
from issues
where
project_uuid = #{projectUuid,jdbcType=VARCHAR}
)
</delete>

<delete id="deleteFileSourcesByProjectUuid">
delete from file_sources where project_uuid=#{rootProjectUuid,jdbcType=VARCHAR}
</delete>
@@ -425,6 +437,14 @@
</foreach>
</delete>

<delete id="deleteIssuesImpactsFromKeys" parameterType="map">
DELETE FROM issues_impacts
WHERE issue_key IN
<foreach collection="issueKeys" open="(" close=")" item="issueKey" separator=",">
#{issueKey,jdbcType=VARCHAR}
</foreach>
</delete>

<delete id="deleteCeScannerContextOfCeActivityByRootUuidOrBefore">
delete from ce_scanner_context
where

+ 49
- 3
server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml View File

@@ -48,14 +48,25 @@
rds.rule_uuid = r.uuid
</sql>

<sql id="leftOuterJoinRulesDefaultImpacts">
left outer join rules_default_impacts rdi on
rdi.rule_uuid = r.uuid
</sql>

<sql id="selectJoinedTablesColumns">
<!-- rule default impacts -->
rdi.uuid as "rdi_uuid",
rdi.rule_uuid as "rdi_ruleUuid",
rdi.software_quality as "rdi_softwareQuality",
rdi.severity as "rdi_severity",
<!-- rule description sections -->
rds.content as "rds_content",
rds.uuid as "rds_uuid",
rds.kee as "rds_kee",
rds.context_key as "rds_contextKey",
rds.context_display_name as "rds_contextDisplayName",
<!-- rule -->
r.uuid as "r_uuid",

<include refid="ruleColumns"/>
</sql>

@@ -65,6 +76,8 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
order by r.uuid
</select>

<resultMap id="ruleResultMap" type="org.sonar.db.rule.RuleDto" autoMapping="true">
@@ -81,18 +94,23 @@
</constructor>
</association>
</collection>
<collection property="defaultImpacts" column="rdi_uuid" notNullColumn="rdi_uuid" javaType="java.util.Set" ofType="Impact">
<id property="uuid" column="rdi_uuid"/>
<result property="softwareQuality" column="rdi_softwareQuality"/>
<result property="severity" column="rdi_severity"/>
</collection>
</resultMap>



<select id="selectEnabled" resultMap="ruleResultMap">
select
<include refid="selectJoinedTablesColumns"/>
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
r.status != 'REMOVED'
order by r.uuid
</select>

<select id="selectByUuid" parameterType="map" resultMap="ruleResultMap">
@@ -101,8 +119,10 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
r.uuid=#{uuid,jdbcType=VARCHAR}
order by r.uuid
</select>

<select id="selectByUuids" parameterType="map" resultMap="ruleResultMap">
@@ -111,10 +131,12 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
<foreach collection="uuids" index="index" item="uuid" open="" separator=" or " close="">
r.uuid=#{uuid,jdbcType=VARCHAR}
</foreach>
order by r.uuid
</select>

<select id="selectByKey" parameterType="map" resultMap="ruleResultMap">
@@ -123,9 +145,11 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
r.plugin_name=#{ruleKey.repository,jdbcType=VARCHAR}
and r.plugin_rule_key=#{ruleKey.rule,jdbcType=VARCHAR}
order by r.uuid
</select>

<select id="selectIndexingRuleExtensionsByIds" parameterType="map" resultType="org.sonar.db.rule.RuleExtensionForIndexingDto">
@@ -165,10 +189,12 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
<foreach collection="ruleKeys" index="index" item="ruleKey" open="" separator=" or " close="">
(r.plugin_name=#{ruleKey.repository,jdbcType=VARCHAR} and r.plugin_rule_key=#{ruleKey.rule,jdbcType=VARCHAR})
</foreach>
order by r.uuid
</select>

<select id="selectByQuery" parameterType="map" resultMap="ruleResultMap">
@@ -177,6 +203,7 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
r.status != 'REMOVED'
<if test="query.repositoryKey!=null">
@@ -189,6 +216,7 @@
and r.plugin_config_key = #{query.configKey,jdbcType=VARCHAR}
</if>
order by
r.uuid,
r.updated_at desc
</select>

@@ -198,12 +226,14 @@
from
rules r
<include refid="leftOuterJoinRulesDescriptionSections"/>
<include refid="leftOuterJoinRulesDefaultImpacts"/>
where
r.status != 'REMOVED' and r.is_external=${_false} and r.is_template=${_false}
and r.rule_type in
<foreach collection="types" item="type" separator="," open="(" close=")">#{type, jdbcType=INTEGER}</foreach>
and r.language in
<foreach collection="languages" item="language" separator="," open="(" close=")">#{language, jdbcType=VARCHAR}</foreach>
order by r.uuid
</select>

<select id="selectByLanguage" parameterType="String" resultType="org.sonar.db.rule.RuleDto" fetchSize="${_scrollFetchSize}"
@@ -321,6 +351,15 @@
)
</insert>

<insert id="insertRuleDefaultImpact" parameterType="Map" useGeneratedKeys="false">
INSERT INTO rules_default_impacts (uuid, rule_uuid, software_quality, severity)
VALUES (
#{dto.uuid,jdbcType=VARCHAR},
#{ruleUuid,jdbcType=VARCHAR},
#{dto.softwareQuality,jdbcType=VARCHAR},
#{dto.severity,jdbcType=VARCHAR})
</insert>

<update id="updateRule" parameterType="org.sonar.db.rule.RuleDto">
update rules set
plugin_key=#{pluginKey,jdbcType=VARCHAR},
@@ -370,6 +409,13 @@
rule_uuid=#{ruleUuid,jdbcType=VARCHAR}
</delete>

<delete id="deleteRuleDefaultImpacts" parameterType="String">
delete from
rules_default_impacts
where
rule_uuid=#{ruleUuid,jdbcType=VARCHAR}
</delete>

<delete id="deleteParams" parameterType="String">
delete from
active_rule_parameters

+ 90
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java View File

@@ -29,15 +29,20 @@ import java.util.Set;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.protobuf.DbIssues;
import org.sonar.db.rule.RuleDto;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;

public class IssueDtoTest {

@@ -153,6 +158,83 @@ public class IssueDtoTest {
assertThat(dto.getTags()).isEmpty();
}

@Test
public void getEffectiveImpacts_whenNoIssueImpactsOverridden_shouldReturnRuleImpacts() {
IssueDto dto = new IssueDto();
dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.SECURITY, Severity.MEDIUM));
dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));

assertThat(dto.getEffectiveImpacts())
.containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
.containsEntry(SoftwareQuality.SECURITY, Severity.MEDIUM)
.containsEntry(SoftwareQuality.RELIABILITY, Severity.LOW);
}

@Test
public void getEffectiveImpacts_whenIssueImpactsOverridden_shouldReturnRuleImpactsOverriddenByIssueImpacts() {
IssueDto dto = new IssueDto();
dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.SECURITY, Severity.MEDIUM));
dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));

dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
dto.addImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.HIGH));

assertThat(dto.getEffectiveImpacts())
.containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.LOW)
.containsEntry(SoftwareQuality.SECURITY, Severity.MEDIUM)
.containsEntry(SoftwareQuality.RELIABILITY, Severity.HIGH);
}

@Test
public void addImpact_whenSoftwareQualityAlreadyDefined_shouldThrowISE() {
IssueDto dto = new IssueDto();
dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));

ImpactDto duplicatedImpact = newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);

assertThatThrownBy(() -> dto.addImpact(duplicatedImpact))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Impact already defined on issue for Software Quality [MAINTAINABILITY]");
}

@Test
public void replaceAllImpacts_whenSoftwareQualityAlreadyDuplicated_shouldThrowISE() {
IssueDto dto = new IssueDto();
dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
dto.addImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
dto.addImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));

Set<ImpactDto> duplicatedImpacts = Set.of(
newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
assertThatThrownBy(() -> dto.replaceAllImpacts(duplicatedImpacts))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Impacts must have unique Software Quality values");
}

@Test
public void replaceAllImpacts_shouldReplaceExistingImpacts() {
IssueDto dto = new IssueDto();
dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
dto.addImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
dto.addImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));

Set<ImpactDto> duplicatedImpacts = Set.of(
newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
newImpactDto(SoftwareQuality.SECURITY, Severity.LOW));

dto.replaceAllImpacts(duplicatedImpacts);

assertThat(dto.getImpacts())
.extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
.containsExactlyInAnyOrder(
tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
tuple(SoftwareQuality.SECURITY, Severity.LOW));

}

@Test
public void setCodeVariants_shouldReturnCodeVariants() {
IssueDto dto = new IssueDto();
@@ -279,4 +361,12 @@ public class IssueDtoTest {
.setCodeVariants(List.of("variant1", "variant2"));
return defaultIssue;
}

public static ImpactDto newImpactDto(SoftwareQuality softwareQuality, Severity severity) {
return new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(softwareQuality)
.setSeverity(severity);
}

}

+ 61
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java View File

@@ -25,19 +25,24 @@ import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.core.util.Uuids;
import org.sonar.db.issue.ImpactDto;

import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.apache.commons.lang.StringUtils.repeat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.db.rule.RuleDto.ERROR_MESSAGE_SECTION_ALREADY_EXISTS;
import static org.sonar.db.rule.RuleTesting.newRule;

public class RuleDtoTest {


public static final String SECTION_KEY = "section key";

@Test
public void fail_if_key_is_too_long() {
assertThatThrownBy(() -> new RuleDto().setRuleKey(repeat("x", 250)))
@@ -164,6 +169,54 @@ public class RuleDtoTest {

}

@Test
public void addDefaultImpact_whenSoftwareQualityAlreadyDefined_shouldThrowISE() {
RuleDto dto = new RuleDto();
dto.addDefaultImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));

ImpactDto duplicatedImpact = newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);

assertThatThrownBy(() -> dto.addDefaultImpact(duplicatedImpact))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Impact already defined on rule for Software Quality [MAINTAINABILITY]");
}

@Test
public void replaceAllDefaultImpacts_whenSoftwareQualityAlreadyDuplicated_shouldThrowISE() {
RuleDto dto = new RuleDto();
dto.addDefaultImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
dto.addDefaultImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
dto.addDefaultImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));

Set<ImpactDto> duplicatedImpacts = Set.of(
newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
assertThatThrownBy(() -> dto.replaceAllDefaultImpacts(duplicatedImpacts))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Impacts must have unique Software Quality values");
}

@Test
public void replaceAllImpacts_shouldReplaceExistingImpacts() {
RuleDto dto = new RuleDto();
dto.addDefaultImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
dto.addDefaultImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
dto.addDefaultImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));

Set<ImpactDto> duplicatedImpacts = Set.of(
newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
newImpactDto(SoftwareQuality.SECURITY, Severity.LOW));

dto.replaceAllDefaultImpacts(duplicatedImpacts);

assertThat(dto.getDefaultImpacts())
.extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
.containsExactlyInAnyOrder(
tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
tuple(SoftwareQuality.SECURITY, Severity.LOW));

}

@NotNull
private static RuleDescriptionSectionDto createSection(String section_key, String contextKey, String contextDisplayName) {
return RuleDescriptionSectionDto.builder()
@@ -178,4 +231,11 @@ public class RuleDtoTest {
.key(section_key)
.build();
}

public static ImpactDto newImpactDto(SoftwareQuality softwareQuality, Severity severity) {
return new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(softwareQuality)
.setSeverity(severity);
}
}

+ 7
- 3
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java View File

@@ -23,6 +23,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
@@ -31,6 +32,7 @@ import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RuleParamType;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.issue.ImpactDto;
import org.sonar.db.rule.RuleDto.Scope;

import static com.google.common.base.Preconditions.checkNotNull;
@@ -62,10 +64,10 @@ public class RuleTesting {
// only static helpers
}


public static RuleDto newRule() {
return newRule(RuleKey.of(randomAlphanumeric(30), randomAlphanumeric(30)));
}

public static RuleDto newRule(RuleDescriptionSectionDto... ruleDescriptionSectionDtos) {
return newRule(randomRuleKey(), ruleDescriptionSectionDtos);
}
@@ -94,6 +96,9 @@ public class RuleTesting {
.setDescriptionFormat(RuleDto.Format.HTML)
.setType(CODE_SMELL)
.setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
.addDefaultImpact(new ImpactDto().setUuid(uuidFactory.create())
.setSoftwareQuality(SoftwareQuality.MAINTAINABILITY)
.setSeverity(org.sonar.api.issue.impact.Severity.HIGH))
.setStatus(RuleStatus.READY)
.setConfigKey("configKey_" + ruleKey.rule())
.setSeverity(Severity.ALL.get(nextInt(Severity.ALL.size())))
@@ -104,7 +109,7 @@ public class RuleTesting {
.setLanguage("lang_" + randomAlphanumeric(3))
.setGapDescription("gapDescription_" + randomAlphanumeric(5))
.setDefRemediationBaseEffort(nextInt(10) + "h")
//voluntarily offset the remediation to be able to detect issues
// voluntarily offset the remediation to be able to detect issues
.setDefRemediationGapMultiplier((nextInt(10) + 10) + "h")
.setDefRemediationFunction("LINEAR_OFFSET")
.setRemediationBaseEffort(nextInt(10) + "h")
@@ -151,7 +156,6 @@ public class RuleTesting {
return newRule(XOO_X2).setLanguage("xoo");
}


public static RuleDto newTemplateRule(RuleKey ruleKey) {
return newRule(ruleKey)
.setIsTemplate(true);

Loading…
Cancel
Save