"PROJECT_UUID" VARCHAR(50) NOT NULL,
"FILE_UUID" VARCHAR(50) NOT NULL,
"LINE_HASHES" CLOB,
+ "LINE_HASHES_VERSION" INTEGER,
"BINARY_DATA" BLOB,
"DATA_TYPE" VARCHAR(20),
"DATA_HASH" VARCHAR(50),
"SRC_HASH" VARCHAR(50),
"REVISION" VARCHAR(100),
"CREATED_AT" BIGINT NOT NULL,
- "UPDATED_AT" BIGINT NOT NULL,
- "LINE_HASHES_VERSION" INTEGER
+ "UPDATED_AT" BIGINT NOT NULL
);
CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID");
CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE");
@CheckForNull
public LineHashVersion selectLineHashesVersion(DbSession dbSession, String fileUuid) {
- Connection connection = dbSession.getConnection();
- PreparedStatement pstmt = null;
- ResultSet rs = null;
- try {
- pstmt = connection.prepareStatement("SELECT line_hashes_version FROM file_sources WHERE file_uuid=? AND data_type=?");
- pstmt.setString(1, fileUuid);
- pstmt.setString(2, Type.SOURCE);
- rs = pstmt.executeQuery();
- if (rs.next()) {
- return LineHashVersion.valueOf(rs.getInt(1));
- }
- return null;
- } catch (SQLException e) {
- throw new IllegalStateException("Fail to read FILE_SOURCES.LINE_HASHES_VERSION of file " + fileUuid, e);
- } finally {
- DbUtils.closeQuietly(connection, pstmt, rs);
- }
+ Integer version = mapper(dbSession).selectLineHashesVersion(fileUuid, Type.SOURCE);
+ return version == null ? LineHashVersion.WITHOUT_SIGNIFICANT_CODE : LineHashVersion.valueOf(version);
}
@CheckForNull
return lineHashesVersion != null ? lineHashesVersion : LineHashVersion.WITHOUT_SIGNIFICANT_CODE.getDbValue();
}
- public FileSourceDto setLineHashesVersion(@Nullable Integer lineHashesVersion) {
+ public FileSourceDto setLineHashesVersion(int lineHashesVersion) {
this.lineHashesVersion = lineHashesVersion;
return this;
}
@CheckForNull
FileSourceDto select(@Param("fileUuid") String fileUuid, @Param("dataType") String dataType);
+ @CheckForNull
+ Integer selectLineHashesVersion(@Param("fileUuid") String fileUuid, @Param("dataType") String dataType);
+
void insert(FileSourceDto dto);
void update(FileSourceDto dto);
*/
package org.sonar.db.source;
-import javax.annotation.Nullable;
-
public enum LineHashVersion {
WITHOUT_SIGNIFICANT_CODE(0), WITH_SIGNIFICANT_CODE(1);
return value;
}
- public static LineHashVersion valueOf(@Nullable Integer version) {
- if (version == null) {
- return LineHashVersion.WITHOUT_SIGNIFICANT_CODE;
- }
+ public static LineHashVersion valueOf(int version) {
if (version > 1 || version < 0) {
throw new IllegalArgumentException("Unknown line hash version: " + version);
}
WHERE project_uuid = #{projectUuid} and data_type=#{dataType}
</select>
+ <select id="selectLineHashesVersion" parameterType="map" resultType="Integer">
+ SELECT line_hashes_version
+ FROM file_sources
+ WHERE file_uuid = #{fileUuid,jdbcType=VARCHAR} and data_type=#{dataType,jdbcType=VARCHAR}
+ </select>
+
<insert id="insert" parameterType="org.sonar.db.source.FileSourceDto" useGeneratedKeys="false">
INSERT INTO file_sources (project_uuid, file_uuid, created_at, updated_at, binary_data, line_hashes, data_hash,
src_hash, data_type, revision, line_hashes_version)
.setDataType(Type.SOURCE)
.setCreatedAt(1500000000000L)
.setUpdatedAt(1500000000001L)
- .setLineHashesVersion(2)
+ .setLineHashesVersion(1)
.setRevision("123456789"));
session.commit();
}
@Test
- public void selectLineHashesVersion_returns_by_default() {
- dbTester.prepareDbUnit(getClass(), "shared.xml");
-
+ public void selectLineHashesVersion_returns_without_significant_code_by_default() {
underTest.insert(session, new FileSourceDto()
.setProjectUuid("PRJ_UUID")
.setFileUuid("FILE2_UUID")
@Test
public void selectLineHashesVersion_succeeds() {
- dbTester.prepareDbUnit(getClass(), "shared.xml");
-
underTest.insert(session, new FileSourceDto()
.setProjectUuid("PRJ_UUID")
.setFileUuid("FILE2_UUID")
.setLineHashes("NEW_LINE_HASHES")
.setDataType(Type.SOURCE)
.setUpdatedAt(1500000000002L)
- .setLineHashesVersion(4)
+ .setLineHashesVersion(1)
.setRevision("987654321"));
session.commit();
@Test
public void should_create_from_int() {
- assertThat(LineHashVersion.valueOf((Integer) null)).isEqualTo(LineHashVersion.WITHOUT_SIGNIFICANT_CODE);
assertThat(LineHashVersion.valueOf(0)).isEqualTo(LineHashVersion.WITHOUT_SIGNIFICANT_CODE);
assertThat(LineHashVersion.valueOf(1)).isEqualTo(LineHashVersion.WITH_SIGNIFICANT_CODE);
}
@Test
- public void should_throw_exception_if_version_is_unknown() {
+ public void should_throw_exception_if_version_is_too_high() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("Unknown line hash version: 2");
LineHashVersion.valueOf(2);
+ }
+ @Test
+ public void should_throw_exception_if_version_is_too_low() {
+ exception.expect(IllegalArgumentException.class);
+ exception.expectMessage("Unknown line hash version: -1");
+ LineHashVersion.valueOf(-1);
}
}
line_hashes="LINE1_HASH\nLINE2_HASH"
src_hash="FILE2_HASH" revision="123456789"
created_at="1500000000000" updated_at="1500000000001" data_type="SOURCE"
- line_hashes_version="2" />
+ line_hashes_version="1" />
</dataset>
line_hashes="NEW_LINE_HASHES"
src_hash="NEW_FILE_HASH" revision="987654321"
created_at="1500000000000" updated_at="1500000000002" data_type="SOURCE"
- line_hashes_version="4" />
+ line_hashes_version="1" />
</dataset>
);
CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID");
CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE");
-CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT");
\ No newline at end of file
+CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT");
+
ImmutableMap.Builder<String, File> builder = ImmutableMap.builder();
for (String fileKey : addedFileKeys) {
Component component = reportFilesByKey.get(fileKey);
- List<String> lineHashes = sourceLinesHash.getMatchingDB(component);
+ List<String> lineHashes = sourceLinesHash.getLineHashesMatchingDBVersion(component);
builder.put(fileKey, new File(component.getReportAttributes().getPath(), lineHashes));
}
return builder.build();
@Override
protected LineHashSequence loadLineHashSequence() {
if (component.getType() == Component.Type.FILE) {
- return new LineHashSequence(sourceLinesHash.getMatchingDB(component));
+ return new LineHashSequence(sourceLinesHash.getLineHashesMatchingDBVersion(component));
} else {
return new LineHashSequence(Collections.emptyList());
}
currentLine = lineRanges.next();
}
- if (currentLine.getLine() == i+1) {
+ if (currentLine.getLine() == i + 1) {
ranges[i] = new LineRange(currentLine.getStartOffset(), currentLine.getEndOffset());
currentLine = null;
}
}
private List<String> getReportLines(Component component) {
- return sourceLinesHash.getMatchingDB(component);
+ return sourceLinesHash.getLineHashesMatchingDBVersion(component);
}
}
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.source.SourceLinesHashRepositoryImpl.LineHashesComputer;
+/**
+ * Generates line hashes from source code included in the report.
+ * Line hashes are versioned. Currently there are 2 possible versions: Hashes created using the entire line, or hashes created using
+ * only the "significant code" part of the line. The "significant code" can be optionally provided by code analyzers, meaning that
+ * the line hash for a given file can be of either versions.
+ * We always persist line hashes taking into account "significant code", if it's provided.
+ * When the line hashes are used for comparison with line hashes stored in the DB, we try to generate them using the same version
+ * as the ones in the DB. This ensures that the line hashes are actually comparable.
+ */
public interface SourceLinesHashRepository {
/**
- * Get line hashes from the report matching the version of the line hashes existing in the report, if possible.
- * The line hashes are cached.
+ * Read from the report the line hashes for a file.
+ * The line hashes will have the version matching the version of the line hashes existing in the report, if possible.
*/
- List<String> getMatchingDB(Component component);
+ List<String> getLineHashesMatchingDBVersion(Component component);
/**
- * The line computer will compute line hashes taking into account significant code (if it was provided by a code analyzer).
- * It will use a cached value, if possible. If it's generated, it's not cached since it's assumed that it won't be
- * needed again after it is persisted.
+ * Get a line hash computer that can be used when persisting the line hashes in the DB.
+ * The version of the line hashes that are generated by the computer will be the one that takes into account significant code,
+ * if it was provided by a code analyzer.
*/
- LineHashesComputer getLineProcessorToPersist(Component component);
+ LineHashesComputer getLineHashesComputerToPersist(Component component);
/**
- * Get the version of line hashes in the report
+ * Get the version of the line hashes for a given component in the report
*/
- Integer getLineHashesVersion(Component component);
+ int getLineHashesVersion(Component component);
}
}
@Override
- public List<String> getMatchingDB(Component component) {
+ public List<String> getLineHashesMatchingDBVersion(Component component) {
return cache.computeIfAbsent(component, this::createLineHashesMatchingDBVersion);
}
@Override
- public Integer getLineHashesVersion(Component component) {
+ public int getLineHashesVersion(Component component) {
if (significantCodeRepository.getRangesPerLine(component).isPresent()) {
return LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue();
} else {
}
@Override
- public LineHashesComputer getLineProcessorToPersist(Component component) {
+ public LineHashesComputer getLineHashesComputerToPersist(Component component) {
boolean cacheHit = cache.contains(component);
// check if line hashes are cached and if we can use it
public void visitFile(Component file) {
try (CloseableIterator<String> linesIterator = sourceLinesRepository.readLines(file);
LineReaders lineReaders = new LineReaders(reportReader, scmInfoRepository, duplicationRepository, file)) {
- LineHashesComputer lineHashesComputer = sourceLinesHash.getLineProcessorToPersist(file);
+ LineHashesComputer lineHashesComputer = sourceLinesHash.getLineHashesComputerToPersist(file);
ComputeFileSourceData computeFileSourceData = new ComputeFileSourceData(linesIterator, lineReaders.readers(), lineHashesComputer);
ComputeFileSourceData.Data fileSourceData = computeFileSourceData.compute();
persistSource(fileSourceData, file, lineReaders.getLatestChangeWithRevision());
for (String line : content) {
computer.addLine(line);
}
- when(sourceLinesHash.getMatchingDB(file)).thenReturn(computer.getLineHashes());
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(file)).thenReturn(computer.getLineHashes());
}
private void mockContentOfFileInDb(String key, @Nullable String[] content) {
@Test
public void load_source_hash_sequences() {
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
Input<DefaultIssue> input = underTest.create(FILE);
assertThat(input.getLineHashSequence()).isNotNull();
@Test
public void load_issues_from_report() {
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setMsg("the message")
@Test
public void load_external_issues_from_report() {
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setMsg("the message")
@Test
public void load_external_issues_from_report_with_default_effort() {
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setMsg("the message")
@Test
public void ignore_issue_from_report() {
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(false);
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setMsg("the message")
@Test
public void ignore_report_issues_on_common_rules() {
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setMsg("the message")
.setRuleRepository(CommonRuleKeys.commonRepositoryForLang("java"))
@Test
public void load_issues_of_compute_engine_common_rules() {
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
DefaultIssue ceIssue = new DefaultIssue()
.setRuleKey(RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "InsufficientCoverage"))
.setMessage("not enough coverage")
@Test
public void ignore_issue_from_common_rule() {
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(false);
- when(sourceLinesHash.getMatchingDB(FILE)).thenReturn(Collections.singletonList("line"));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
DefaultIssue ceIssue = new DefaultIssue()
.setRuleKey(RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "InsufficientCoverage"))
.setMessage("not enough coverage")
}
private void setLineHashesInReport(Component component, String[] content) {
- when(sourceLinesHash.getMatchingDB(component)).thenReturn(Arrays.asList(content));
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(component)).thenReturn(Arrays.asList(content));
}
}
public void should_generate_correct_version_of_line_hashes() {
Component component = createComponent(1);
- underTest.getMatchingDB(component);
+ underTest.getLineHashesMatchingDBVersion(component);
}
@Test
public void should_create_hash_without_significant_code_if_db_has_no_significant_code() {
when(dbLineHashVersion.hasLineHashesWithSignificantCode(file)).thenReturn(false);
- List<String> lineHashes = underTest.getMatchingDB(file);
+ List<String> lineHashes = underTest.getLineHashesMatchingDBVersion(file);
assertLineHashes(lineHashes, "line1", "line2", "line3");
verify(dbLineHashVersion).hasLineHashesWithSignificantCode(file);
when(dbLineHashVersion.hasLineHashesWithSignificantCode(file)).thenReturn(true);
when(significantCodeRepository.getRangesPerLine(file)).thenReturn(Optional.empty());
- List<String> lineHashes = underTest.getMatchingDB(file);
+ List<String> lineHashes = underTest.getLineHashesMatchingDBVersion(file);
assertLineHashes(lineHashes, "line1", "line2", "line3");
verify(dbLineHashVersion).hasLineHashesWithSignificantCode(file);
when(dbLineHashVersion.hasLineHashesWithSignificantCode(file)).thenReturn(true);
when(significantCodeRepository.getRangesPerLine(file)).thenReturn(Optional.of(lineRanges));
- List<String> lineHashes = underTest.getMatchingDB(file);
+ List<String> lineHashes = underTest.getLineHashesMatchingDBVersion(file);
assertLineHashes(lineHashes, "l", "", "ine3");
verify(dbLineHashVersion).hasLineHashesWithSignificantCode(file);
when(dbLineHashVersion.hasLineHashesWithSignificantCode(file)).thenReturn(true);
when(significantCodeRepository.getRangesPerLine(file)).thenReturn(Optional.of(lineRanges));
- LineHashesComputer hashesComputer = underTest.getLineProcessorToPersist(file);
+ LineHashesComputer hashesComputer = underTest.getLineHashesComputerToPersist(file);
assertThat(hashesComputer).isInstanceOf(CachedLineHashesComputer.class);
assertThat(hashesComputer.getResult()).isEqualTo(lineHashes);
when(dbLineHashVersion.hasLineHashesWithSignificantCode(file)).thenReturn(false);
when(significantCodeRepository.getRangesPerLine(file)).thenReturn(Optional.empty());
- LineHashesComputer hashesComputer = underTest.getLineProcessorToPersist(file);
+ LineHashesComputer hashesComputer = underTest.getLineHashesComputerToPersist(file);
assertThat(hashesComputer).isInstanceOf(CachedLineHashesComputer.class);
assertThat(hashesComputer.getResult()).isEqualTo(lineHashes);
when(dbLineHashVersion.hasLineHashesWithSignificantCode(file)).thenReturn(false);
when(significantCodeRepository.getRangesPerLine(file)).thenReturn(Optional.of(lineRanges));
- LineHashesComputer hashesComputer = underTest.getLineProcessorToPersist(file);
+ LineHashesComputer hashesComputer = underTest.getLineHashesComputerToPersist(file);
assertThat(hashesComputer).isInstanceOf(SignificantCodeLineHashesComputer.class);
}
@Before
public void setup() {
when(system2.now()).thenReturn(NOW);
- when(sourceLinesHashRepository.getLineProcessorToPersist(Mockito.any(Component.class))).thenReturn(lineHashesComputer);
+ when(sourceLinesHashRepository.getLineHashesComputerToPersist(Mockito.any(Component.class))).thenReturn(lineHashesComputer);
underTest = new PersistFileSourcesStep(dbClient, system2, treeRootHolder, reportReader, fileSourceRepository, scmInfoRepository,
duplicationRepository, sourceLinesHashRepository);
}
private final int endOffset;
public LineRange(int startOffset, int endOffset) {
+ Preconditions.checkArgument(startOffset >= 0, "Start offset not valid: %s", startOffset);
Preconditions.checkArgument(startOffset <= endOffset, "Line range is not valid: %s must be greater or equal than %s", endOffset, startOffset);
this.startOffset = startOffset;
this.endOffset = endOffset;
exception.expectMessage("Line range is not valid: 1 must be greater or equal than 2");
new LineRange(2, 1);
}
+
+ @Test
+ public void should_throw_ISE_if_startOffset_is_invalid() {
+ exception.expect(IllegalArgumentException.class);
+ exception.expectMessage("Start offset not valid: -1");
+ new LineRange(-1, 1);
+ }
@Test
public void check_getters() {