@@ -25,6 +25,7 @@ import java.util.Optional; | |||
import java.util.Set; | |||
import org.sonar.db.Dao; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.Pagination; | |||
import org.sonar.db.RowNotFoundException; | |||
import org.sonar.db.WildcardPosition; | |||
import org.sonar.db.component.ComponentDto; | |||
@@ -33,6 +34,7 @@ import static org.sonar.db.DaoUtils.buildLikeValue; | |||
import static org.sonar.db.DatabaseUtils.executeLargeInputs; | |||
public class IssueDao implements Dao { | |||
public static final int DEFAULT_PAGE_SIZE = 1000; | |||
public Optional<IssueDto> selectByKey(DbSession session, String key) { | |||
return Optional.ofNullable(mapper(session).selectByKey(key)); | |||
@@ -40,7 +42,7 @@ public class IssueDao implements Dao { | |||
public IssueDto selectOrFailByKey(DbSession session, String key) { | |||
Optional<IssueDto> issue = selectByKey(session, key); | |||
if (!issue.isPresent()) { | |||
if (issue.isEmpty()) { | |||
throw new RowNotFoundException(String.format("Issue with key '%s' does not exist", key)); | |||
} | |||
return issue.get(); | |||
@@ -56,6 +58,10 @@ public class IssueDao implements Dao { | |||
return executeLargeInputs(keys, mapper(session)::selectByKeys); | |||
} | |||
public List<IssueDto> selectByComponentUuidPaginated(DbSession session, String componentUuid, int page) { | |||
return mapper(session).selectByComponentUuidPaginated(componentUuid, Pagination.forPage(page).andSize(DEFAULT_PAGE_SIZE)); | |||
} | |||
public Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(DbSession session, String projectUuid) { | |||
return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid); | |||
} |
@@ -71,6 +71,7 @@ public final class IssueDto implements Serializable { | |||
private String assigneeUuid; | |||
private String authorLogin; | |||
private String issueAttributes; | |||
private String securityStandards; | |||
private byte[] locations; | |||
private long createdAt; | |||
private long updatedAt; | |||
@@ -357,6 +358,15 @@ public final class IssueDto implements Serializable { | |||
return this; | |||
} | |||
public IssueDto setSecurityStandards(@Nullable String s) { | |||
this.securityStandards = s; | |||
return this; | |||
} | |||
public Set<String> getSecurityStandards() { | |||
return RuleDefinitionDto.deserializeSecurityStandardsString(securityStandards); | |||
} | |||
/** | |||
* Technical date | |||
*/ |
@@ -24,6 +24,7 @@ import java.util.List; | |||
import java.util.Set; | |||
import org.apache.ibatis.annotations.Param; | |||
import org.apache.ibatis.session.ResultHandler; | |||
import org.sonar.db.Pagination; | |||
import org.sonar.db.component.ComponentDto; | |||
public interface IssueMapper { | |||
@@ -36,6 +37,9 @@ public interface IssueMapper { | |||
List<IssueDto> selectByKeys(List<String> keys); | |||
List<IssueDto> selectByComponentUuidPaginated(@Param("componentUuid") String componentUuid, | |||
@Param("pagination") Pagination pagination); | |||
List<IssueDto> selectByKeysIfNotUpdatedAt(@Param("keys") List<String> keys, @Param("updatedAt") long updatedAt); | |||
List<PrIssueDto> selectOpenByComponentUuids(List<String> componentUuids); |
@@ -30,6 +30,7 @@ | |||
r.plugin_rule_key as ruleKey, | |||
r.plugin_name as ruleRepo, | |||
r.language as language, | |||
r.security_standards as securityStandards, | |||
p.kee as componentKey, | |||
i.component_uuid as componentUuid, | |||
p.module_uuid as moduleUuid, | |||
@@ -40,6 +41,33 @@ | |||
i.issue_type as type | |||
</sql> | |||
<sql id="issueColumnsInInnerQuery"> | |||
i.kee, | |||
i.rule_uuid, | |||
i.severity, | |||
i.manual_severity, | |||
i.message, | |||
i.line, | |||
i.locations, | |||
i.gap, | |||
i.effort, | |||
i.status, | |||
i.resolution, | |||
i.checksum, | |||
i.assignee, | |||
i.author_login, | |||
i.tags, | |||
i.issue_attributes, | |||
i.issue_creation_date, | |||
i.issue_update_date, | |||
i.issue_close_date, | |||
i.created_at, | |||
i.updated_at, | |||
i.component_uuid, | |||
i.project_uuid, | |||
i.issue_type | |||
</sql> | |||
<sql id="sortColumn"> | |||
<if test="query.sort() != null">, | |||
<choose> | |||
@@ -324,5 +352,55 @@ | |||
) i2 | |||
group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak | |||
</select> | |||
<select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue"> | |||
select | |||
<include refid="issueColumns"/> | |||
from issues i | |||
inner join rules r on r.uuid=i.rule_uuid | |||
inner join components p on p.uuid=i.component_uuid | |||
inner join components root on root.uuid=i.project_uuid | |||
where i.project_uuid=#{componentUuid,jdbcType=VARCHAR} | |||
order by i.kee | |||
limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER} | |||
</select> | |||
<select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="mssql"> | |||
select | |||
<include refid="issueColumns"/> | |||
from | |||
(select | |||
row_number() over(order by i.kee) as row_number, | |||
<include refid="issueColumnsInInnerQuery" /> | |||
from issues i | |||
where i.project_uuid=#{componentUuid,jdbcType=VARCHAR} | |||
order by row_number asc | |||
offset #{pagination.offset} rows | |||
fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only) i | |||
inner join rules r on r.uuid=i.rule_uuid | |||
inner join components p on p.uuid=i.component_uuid | |||
inner join components root on root.uuid=i.project_uuid | |||
</select> | |||
<select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="oracle"> | |||
select | |||
<include refid="issueColumns"/> | |||
from | |||
(select <include refid="issueColumnsInInnerQuery"/> from ( | |||
select rownum as rn, t.* from ( | |||
select | |||
<include refid="issueColumnsInInnerQuery"/> | |||
from issues i | |||
where i.project_uuid=#{componentUuid,jdbcType=VARCHAR} | |||
order by i.kee | |||
) t | |||
) i | |||
where | |||
i.rn between #{pagination.startRowNumber,jdbcType=INTEGER} and #{pagination.endRowNumber,jdbcType=INTEGER} | |||
order by i.rn asc) i | |||
inner join rules r on r.uuid=i.rule_uuid | |||
inner join components p on p.uuid=i.component_uuid | |||
inner join components root on root.uuid=i.project_uuid | |||
</select> | |||
</mapper> | |||
@@ -398,6 +398,7 @@ ALTER TABLE "ISSUE_CHANGES" ADD CONSTRAINT "PK_ISSUE_CHANGES" PRIMARY KEY("UUID" | |||
CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES"("ISSUE_KEY"); | |||
CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES"("KEE"); | |||
CREATE INDEX "ISSUE_CHANGES_PROJECT_UUID" ON "ISSUE_CHANGES"("PROJECT_UUID"); | |||
CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY_TYPE" ON "ISSUE_CHANGES"("ISSUE_KEY", "CHANGE_TYPE"); | |||
CREATE TABLE "ISSUES"( | |||
"KEE" VARCHAR(50) NOT NULL, |
@@ -128,6 +128,17 @@ public class IssueDaoTest { | |||
assertThat(issues).extracting("key").containsOnly("I1", "I2"); | |||
} | |||
@Test | |||
public void selectByComponentUuidPaginated() { | |||
// contains I1 and I2 | |||
prepareTables(); | |||
List<IssueDto> issues = underTest.selectByComponentUuidPaginated(db.getSession(), PROJECT_UUID, 1); | |||
// results are not ordered, so do not use "containsExactly" | |||
assertThat(issues).extracting("key").containsOnly("I1", "I2"); | |||
} | |||
@Test | |||
public void scrollNonClosedByComponentUuid() { | |||
RuleDefinitionDto rule = db.rules().insert(); |
@@ -0,0 +1,51 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.v91; | |||
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.sql.CreateIndexBuilder; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
public class CreateIndexForIssueChangesOnIssueKeyAndChangeType extends DdlChange { | |||
private static final String TABLE_NAME = "issue_changes"; | |||
private static final String INDEX_NAME = "issue_changes_issue_key_type"; | |||
public CreateIndexForIssueChangesOnIssueKeyAndChangeType(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
try (Connection c = getDatabase().getDataSource().getConnection()) { | |||
if (!DatabaseUtils.indexExistsIgnoreCase(TABLE_NAME, INDEX_NAME, c)) { | |||
context.execute(new CreateIndexBuilder() | |||
.setTable(TABLE_NAME) | |||
.setName(INDEX_NAME) | |||
.addColumn("issue_key") | |||
.addColumn("change_type") | |||
.setUnique(false) | |||
.build()); | |||
} | |||
} | |||
} | |||
} |
@@ -43,6 +43,7 @@ public class DbVersion91 implements DbVersion { | |||
.add(6015, "Create 'portfolio_projects' table", CreatePortfolioProjectsTable.class) | |||
.add(6016, "Create unique index for 'portfolio_projects'", CreateIndexForPortfolioProjects.class) | |||
.add(6017, "Migrate portfolios to new tables", MigratePortfoliosToNewTables.class) | |||
.add(6018, "Create index for 'issue_changes' on 'issue_key' and 'change_type'", CreateIndexForIssueChangesOnIssueKeyAndChangeType.class) | |||
; | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.v91; | |||
import java.sql.SQLException; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.db.CoreDbTester; | |||
public class CreateIndexForIssueChangesOnIssueKeyAndChangeTypeTest { | |||
private final static String TABLE_NAME = "issue_changes"; | |||
private final static String INDEX_NAME = "issue_changes_issue_key_type"; | |||
@Rule | |||
public final CoreDbTester db = CoreDbTester.createForSchema(CreateIndexForIssueChangesOnIssueKeyAndChangeTypeTest.class, "schema.sql"); | |||
private final CreateIndexForIssueChangesOnIssueKeyAndChangeType underTest = new CreateIndexForIssueChangesOnIssueKeyAndChangeType(db.database()); | |||
@Test | |||
public void should_create_index() throws SQLException { | |||
db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME); | |||
underTest.execute(); | |||
db.assertIndex(TABLE_NAME, INDEX_NAME, "issue_key", "change_type"); | |||
} | |||
@Test | |||
public void migration_should_be_reentrant() throws SQLException { | |||
db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME); | |||
underTest.execute(); | |||
//re-entrant | |||
underTest.execute(); | |||
db.assertIndex(TABLE_NAME, INDEX_NAME, "issue_key", "change_type"); | |||
} | |||
} |
@@ -41,7 +41,7 @@ public class DbVersion91Test { | |||
@Test | |||
public void verify_migration_count() { | |||
verifyMigrationCount(underTest, 17); | |||
verifyMigrationCount(underTest, 18); | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
CREATE TABLE "ISSUE_CHANGES"( | |||
"UUID" VARCHAR(40) NOT NULL, | |||
"KEE" VARCHAR(50), | |||
"ISSUE_KEY" VARCHAR(50) NOT NULL, | |||
"USER_LOGIN" VARCHAR(255), | |||
"CHANGE_TYPE" VARCHAR(20), | |||
"CHANGE_DATA" CLOB, | |||
"CREATED_AT" BIGINT, | |||
"UPDATED_AT" BIGINT, | |||
"ISSUE_CHANGE_CREATION_DATE" BIGINT, | |||
"PROJECT_UUID" VARCHAR(50) NOT NULL | |||
); | |||
ALTER TABLE "ISSUE_CHANGES" ADD CONSTRAINT "PK_ISSUE_CHANGES" PRIMARY KEY("UUID"); | |||
CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES"("ISSUE_KEY"); | |||
CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES"("KEE"); | |||
CREATE INDEX "ISSUE_CHANGES_PROJECT_UUID" ON "ISSUE_CHANGES"("PROJECT_UUID"); |