import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
+import com.hazelcast.internal.util.MutableLong;
import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import javax.annotation.Nullable;
+import org.apache.ibatis.cursor.Cursor;
import org.slf4j.LoggerFactory;
import org.sonar.api.rule.RuleKey;
import org.sonar.ce.task.projectexport.component.ComponentRepository;
import org.sonar.ce.task.projectexport.steps.ProjectHolder;
import org.sonar.ce.task.projectexport.steps.StreamWriter;
import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.db.DatabaseUtils;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.project.ProjectExportMapper;
import org.sonar.db.protobuf.DbIssues;
import static java.lang.String.format;
-import static org.sonar.ce.task.projectexport.util.ResultSetUtils.defaultIfNull;
-import static org.sonar.ce.task.projectexport.util.ResultSetUtils.emptyIfNull;
public class ExportIssuesStep implements ComputationStep {
- private static final String RULE_STATUS_REMOVED = "REMOVED";
- private static final String ISSUE_STATUS_CLOSED = "CLOSED";
-
- // ordered by rule_id to reduce calls to RuleRepository
- private static final String QUERY = "select" +
- " i.kee, r.uuid, r.plugin_rule_key, r.plugin_name, i.issue_type," +
- " i.component_uuid, i.message, i.line, i.checksum, i.status," +
- " i.resolution, i.severity, i.manual_severity, i.gap, effort," +
- " i.assignee, i.author_login, i.tags, i.issue_creation_date," +
- " i.issue_update_date, i.issue_close_date, i.locations, i.project_uuid," +
- " i.rule_description_context_key, i.message_formattings, i.code_variants, " +
- " ii.software_quality, ii.severity" +
- " from issues i" +
- " join rules r on r.uuid = i.rule_uuid and r.status <> ?" +
- " join components p on p.uuid = i.project_uuid" +
- " join project_branches pb on pb.uuid = p.uuid" +
- " left outer join issues_impacts ii on i.kee = ii.issue_key" +
- " where pb.project_uuid = ? and pb.branch_type = 'BRANCH' and pb.exclude_from_purge=?" +
- " and i.status <> ?" +
- " order by" +
- " i.rule_uuid asc, i.kee, i.created_at asc";
private final DbClient dbClient;
private final ProjectHolder projectHolder;
@Override
public void execute(Context context) {
- long count = 0;
+ MutableLong count = MutableLong.valueOf(0);
try (
StreamWriter<ProjectDump.Issue> output = dumpWriter.newStreamWriter(DumpElement.ISSUES);
DbSession dbSession = dbClient.openSession(false);
- PreparedStatement stmt = createStatement(dbSession);
- ResultSet rs = stmt.executeQuery()) {
- ProjectDump.Issue.Builder builder = ProjectDump.Issue.newBuilder();
- ProjectDump.Issue previousIssue = null;
- while (rs.next()) {
- String issueUuid = rs.getString(1);
- if ((!rs.isFirst() && !previousIssue.getUuid().equals(issueUuid))) {
- output.write(previousIssue);
- count++;
- }
- ProjectDump.Issue issue = mergeIssue(previousIssue, builder, rs);
- if (rs.isLast()) {
+ Cursor<IssueDto> issueDtoCursor = dbSession.getMapper(ProjectExportMapper.class).scrollIssueForExport(projectHolder.projectDto().getUuid())) {
+ ProjectDump.Issue.Builder issueBuilder = ProjectDump.Issue.newBuilder();
+ issueDtoCursor
+ .forEach(issueDto -> {
+ ProjectDump.Issue issue = convertToIssue(issueDto, issueBuilder);
output.write(issue);
- count++;
- }
- previousIssue = issue;
- }
-
- LoggerFactory.getLogger(getClass()).debug("{} issues exported", count);
+ count.getAndInc();
+ });
+ LoggerFactory.getLogger(getClass()).debug("{} issues exported", count.value);
} catch (Exception e) {
- throw new IllegalStateException(format("Issue export failed after processing %d issues successfully", count), e);
+ throw new IllegalStateException(format("Issue export failed after processing %d issues successfully", count.value), e);
}
}
- private PreparedStatement createStatement(DbSession dbSession) throws SQLException {
- PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(dbSession, QUERY);
- try {
- stmt.setString(1, RULE_STATUS_REMOVED);
- stmt.setString(2, projectHolder.projectDto().getUuid());
- stmt.setBoolean(3, true);
- stmt.setString(4, ISSUE_STATUS_CLOSED);
- return stmt;
- } catch (Exception t) {
- DatabaseUtils.closeQuietly(stmt);
- throw t;
- }
- }
+ private ProjectDump.Issue convertToIssue(IssueDto issueDto, ProjectDump.Issue.Builder builder) {
- private ProjectDump.Issue mergeIssue(@Nullable ProjectDump.Issue previousIssue, ProjectDump.Issue.Builder builder, ResultSet rs) throws SQLException {
- builder.clear();
- String issueUuid = rs.getString(1);
- setRule(builder, rs);
+ String ruleRef = registerRule(issueDto);
builder
- .setUuid(issueUuid)
- .setType(rs.getInt(5))
- .setComponentRef(componentRepository.getRef(rs.getString(6)))
- .setMessage(emptyIfNull(rs, 7))
- .setLine(rs.getInt(8))
- .setChecksum(emptyIfNull(rs, 9))
- .setStatus(emptyIfNull(rs, 10))
- .setResolution(emptyIfNull(rs, 11))
- .setSeverity(emptyIfNull(rs, 12))
- .setManualSeverity(rs.getBoolean(13))
- .setGap(defaultIfNull(rs, 14, IssueDumpElement.NO_GAP))
- .setEffort(defaultIfNull(rs, 15, IssueDumpElement.NO_EFFORT))
- .setAssignee(emptyIfNull(rs, 16))
- .setAuthor(emptyIfNull(rs, 17))
- .setTags(emptyIfNull(rs, 18))
- .setIssueCreatedAt(rs.getLong(19))
- .setIssueUpdatedAt(rs.getLong(20))
- .setIssueClosedAt(rs.getLong(21))
- .setProjectUuid(rs.getString(23))
- .setCodeVariants(emptyIfNull(rs, 26));
- Optional.ofNullable(rs.getString(24)).ifPresent(builder::setRuleDescriptionContextKey);
- setLocations(builder, rs, issueUuid);
- setMessageFormattings(builder, rs, issueUuid);
-
- mergeImpacts(builder, rs, previousIssue, issueUuid);
+ .clear()
+ .setRuleRef(ruleRef)
+ .setUuid(issueDto.getKee())
+ .setComponentRef(componentRepository.getRef(issueDto.getComponentUuid()))
+ .setType(issueDto.getType())
+ .setMessage(Optional.of(issueDto).map(IssueDto::getMessage).orElse(""))
+ .setLine(Optional.of(issueDto).map(IssueDto::getLine).orElse(0))
+ .setChecksum(Optional.of(issueDto).map(IssueDto::getChecksum).orElse(""))
+ .setStatus(Optional.of(issueDto).map(IssueDto::getStatus).orElse(""))
+ .setResolution(Optional.of(issueDto).map(IssueDto::getResolution).orElse(""))
+ .setSeverity(Optional.of(issueDto).map(IssueDto::getSeverity).orElse(""))
+ .setManualSeverity(issueDto.isManualSeverity())
+ .setGap(Optional.of(issueDto).map(IssueDto::getGap).orElse(IssueDumpElement.NO_GAP))
+ .setEffort(Optional.of(issueDto).map(IssueDto::getEffort).orElse(IssueDumpElement.NO_EFFORT))
+ .setAssignee(Optional.of(issueDto).map(IssueDto::getAssigneeUuid).orElse(""))
+ .setAuthor(Optional.of(issueDto).map(IssueDto::getAuthorLogin).orElse(""))
+ .setTags(Optional.of(issueDto).map(IssueDto::getTagsString).orElse(""))
+ .setIssueCreatedAt(Optional.of(issueDto).map(IssueDto::getIssueCreationTime).orElse(0L))
+ .setIssueUpdatedAt(Optional.of(issueDto).map(IssueDto::getIssueUpdateTime).orElse(0L))
+ .setIssueClosedAt(Optional.of(issueDto).map(IssueDto::getIssueCloseTime).orElse(0L))
+ .setProjectUuid(issueDto.getProjectUuid())
+ .setCodeVariants(Optional.of(issueDto).map(IssueDto::getCodeVariantsString).orElse(""));
+ setLocations(builder, issueDto);
+ setMessageFormattings(builder, issueDto);
+ mergeImpacts(builder, issueDto);
return builder.build();
}
- private static void mergeImpacts(ProjectDump.Issue.Builder builder, ResultSet rs, @Nullable ProjectDump.Issue previousIssue, String issueUuid)
- throws SQLException {
- String softwareQualityFromDatabase = rs.getString(27);
- if (softwareQualityFromDatabase == null) {
- return;
- }
- ProjectDump.Impact impact = ProjectDump.Impact.newBuilder()
- .setSoftwareQuality(ProjectDump.SoftwareQuality.valueOf(softwareQualityFromDatabase))
- .setSeverity(ProjectDump.Severity.valueOf(rs.getString(28)))
- .build();
-
- builder.addImpacts(impact);
- if (previousIssue != null && previousIssue.getUuid().equals(issueUuid)) {
- builder.addAllImpacts(previousIssue.getImpactsList());
- }
+ private static void mergeImpacts(ProjectDump.Issue.Builder builder, IssueDto issueDto) {
+ issueDto.getImpacts()
+ .stream()
+ .map(impactDto -> ProjectDump.Impact.newBuilder()
+ .setSoftwareQuality(ProjectDump.SoftwareQuality.valueOf(impactDto.getSoftwareQuality().name()))
+ .setSeverity(ProjectDump.Severity.valueOf(impactDto.getSeverity().name()))
+ .build())
+ .forEach(builder::addImpacts);
}
- private void setRule(ProjectDump.Issue.Builder builder, ResultSet rs) throws SQLException {
- String ruleUuid = rs.getString(2);
- String ruleKey = rs.getString(3);
- String repositoryKey = rs.getString(4);
- builder.setRuleRef(ruleRegistrar.register(ruleUuid, repositoryKey, ruleKey).ref());
+ private String registerRule(IssueDto issueDto) {
+ String ruleUuid = issueDto.getRuleUuid();
+ RuleKey ruleKey = issueDto.getRuleKey();
+ return ruleRegistrar.register(ruleUuid, ruleKey).ref();
}
- private static void setLocations(ProjectDump.Issue.Builder builder, ResultSet rs, String issueUuid) throws SQLException {
+ private static void setLocations(ProjectDump.Issue.Builder builder, IssueDto issueDto) {
try {
- byte[] bytes = rs.getBytes(22);
+ byte[] bytes = issueDto.getLocations();
if (bytes != null) {
// fail fast, ensure we can read data from DB
DbIssues.Locations.parseFrom(bytes);
builder.setLocations(ByteString.copyFrom(bytes));
}
} catch (InvalidProtocolBufferException e) {
- throw new IllegalStateException(format("Fail to read locations from DB for issue %s", issueUuid), e);
+ throw new IllegalStateException(format("Fail to read locations from DB for issue %s", issueDto.getKee()), e);
}
}
- private static void setMessageFormattings(ProjectDump.Issue.Builder builder, ResultSet rs, String issueUuid) throws SQLException {
+ private static void setMessageFormattings(ProjectDump.Issue.Builder builder, IssueDto issueDto) {
try {
- byte[] bytes = rs.getBytes(25);
+ byte[] bytes = issueDto.getMessageFormattings();
if (bytes != null) {
// fail fast, ensure we can read data from DB
DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.parseFrom(bytes);
}
}
} catch (InvalidProtocolBufferException e) {
- throw new IllegalStateException(format("Fail to read message formattings from DB for issue %s", issueUuid), e);
+ throw new IllegalStateException(format("Fail to read message formattings from DB for issue %s", issueDto.getKee()), e);
}
}
this.ruleRepository = ruleRepository;
}
- public Rule register(String ruleUuid, String repositoryKey, String ruleKey) {
+ public Rule register(String ruleUuid, RuleKey ruleKey) {
if (Objects.equals(previousRuleUuid, ruleUuid)) {
return previousRule;
}
- return lookup(ruleUuid, RuleKey.of(repositoryKey, ruleKey));
+ return lookup(ruleUuid, ruleKey);
}
private Rule lookup(String ruleUuid, RuleKey ruleKey) {
(ncp.branch_uuid is null OR (pb.branch_type='BRANCH' AND pb.exclude_from_purge=${_true}))
</select>
- <select id="scrollAdhocRulesForExport" parameterType="map" resultMap="org.sonar.db.rule.RuleMapper.ruleResultMap" fetchSize="${_scrollFetchSize}"
+ <select id="scrollAdhocRulesForExport" parameterType="map" resultMap="org.sonar.db.rule.RuleMapper.ruleResultMap"
+ fetchSize="${_scrollFetchSize}"
resultSetType="FORWARD_ONLY" resultOrdered="true">
select
r.uuid as "r_uuid",
rules r
left outer join rules_default_impacts rdi on rdi.rule_uuid = r.uuid
where
- r.status <> 'REMOVED' and r.is_ad_hoc = true
+ r.status != 'REMOVED' and r.is_ad_hoc = ${_true}
and exists(
select i.kee
from issues i
and pb.project_uuid = #{projectUuid,jdbcType=VARCHAR}
and pb.branch_type = 'BRANCH'
and pb.exclude_from_purge = ${_true}
- and i.status <> 'CLOSED')
+ and i.status != 'CLOSED')
order by r.uuid
</select>
+
+
+ <select id="scrollIssueForExport" parameterType="map" resultMap="org.sonar.db.issue.IssueMapper.issueResultMap"
+ fetchSize="${_scrollFetchSize}"
+ resultSetType="FORWARD_ONLY" resultOrdered="true">
+ select i.kee as kee,
+ r.uuid as ruleUuid,
+ r.plugin_rule_key as ruleKey,
+ r.plugin_name as ruleRepo,
+ i.issue_type as type,
+ i.component_uuid as componentUuid,
+ i.message as message,
+ i.line as line,
+ i.checksum as checksum,
+ i.status as status,
+ i.resolution as resolution,
+ i.severity as severity,
+ i.manual_severity as manualSeverity,
+ i.gap as gap,
+ i.effort as effort,
+ i.assignee assigneeUuid,
+ i.author_login as authorLogin,
+ i.tags as tagsString,
+ i.issue_creation_date as issueCreationTime,
+ i.issue_update_date as issueUpdateTime,
+ i.issue_close_date as issueCloseTime,
+ i.created_at as createdAt,
+ i.updated_at as updatedAt,
+ i.locations as locations,
+ i.project_uuid as projectUuid,
+ i.rule_description_context_key as ruleDescriptionContextKey,
+ i.message_formattings as messageFormattings,
+ i.code_variants as codeVariantsString,
+ ii.software_quality as ii_softwareQuality,
+ ii.severity as ii_severity,
+ ii.uuid as ii_uuid,
+ rdi.uuid as rdi_uuid,
+ rdi.software_quality as rdi_softwareQuality,
+ rdi.severity as rdi_severity
+ from issues i
+ join rules r on r.uuid = i.rule_uuid and r.status != 'REMOVED'
+ join components p on p.uuid = i.project_uuid
+ join project_branches pb on pb.uuid = p.uuid
+ left outer join issues_impacts ii on i.kee = ii.issue_key
+ left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
+ where pb.project_uuid = #{projectUuid,jdbcType=VARCHAR}
+ and pb.branch_type = 'BRANCH'
+ and pb.exclude_from_purge= ${_true}
+ and i.status != 'CLOSED'
+ order by i.rule_uuid asc, i.kee, i.created_at asc
+ </select>
+
</mapper>