Skip to content

Commit 991b9e3

Browse files
SLCORE-1819 Migrate existing known findings to the new database
1 parent f5ce418 commit 991b9e3

File tree

10 files changed

+247
-47
lines changed

10 files changed

+247
-47
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* SonarLint Core - Commons
3+
* Copyright (C) 2016-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.core.commons.storage.repository;
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import org.sonarsource.sonarlint.core.commons.KnownFinding;
25+
26+
public record Findings(List<KnownFinding> issues, List<KnownFinding> hotspots) {
27+
public Findings mergeWith(Findings other) {
28+
var mergedIssues = new ArrayList<>(issues);
29+
mergedIssues.addAll(other.issues);
30+
var mergedHotspots = new ArrayList<KnownFinding>(hotspots);
31+
mergedHotspots.addAll(other.hotspots);
32+
return new Findings(mergedIssues, mergedHotspots);
33+
}
34+
}

backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepository.java

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@
2121

2222
import java.nio.file.Path;
2323
import java.time.LocalDateTime;
24-
import java.time.ZoneId;
24+
import java.time.ZoneOffset;
2525
import java.util.List;
26+
import java.util.Map;
27+
import java.util.stream.Stream;
2628
import org.jooq.Configuration;
2729
import org.jooq.Record;
2830
import org.sonarsource.sonarlint.core.commons.KnownFinding;
2931
import org.sonarsource.sonarlint.core.commons.KnownFindingType;
3032
import org.sonarsource.sonarlint.core.commons.LineWithHash;
3133
import org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;
3234
import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;
35+
import org.sonarsource.sonarlint.core.commons.storage.model.tables.records.KnownFindingsRecord;
3336

3437
import static org.sonarsource.sonarlint.core.commons.storage.model.Tables.KNOWN_FINDINGS;
3538

@@ -41,6 +44,47 @@ public KnownFindingsRepository(SonarLintDatabase database) {
4144
this.database = database;
4245
}
4346

47+
public void storeFindings(Map<String, Map<Path, Findings>> findingsPerFilePerConfigScopeId) {
48+
database.dsl().batchMerge(findingsPerFilePerConfigScopeId.entrySet().stream()
49+
.flatMap(configScopeEntry -> {
50+
var configScopeId = configScopeEntry.getKey();
51+
return configScopeEntry.getValue().entrySet().stream()
52+
.flatMap(fileEntry -> {
53+
var filePath = fileEntry.getKey();
54+
var findings = fileEntry.getValue();
55+
return Stream.<KnownFindingsRecord>concat(
56+
findings.issues().stream()
57+
.map(finding -> createRecord(finding, configScopeId, filePath, KnownFindingType.ISSUE)),
58+
findings.hotspots().stream()
59+
.map(finding -> createRecord(finding, configScopeId, filePath, KnownFindingType.HOTSPOT)));
60+
});
61+
})
62+
.toList())
63+
.execute();
64+
}
65+
66+
private static KnownFindingsRecord createRecord(KnownFinding finding, String configScopeId, Path filePath, KnownFindingType type) {
67+
var textRangeWithHash = finding.getTextRangeWithHash();
68+
var lineWithHash = finding.getLineWithHash();
69+
var introductionDate = LocalDateTime.ofInstant(finding.getIntroductionDate(), ZoneOffset.UTC);
70+
return new KnownFindingsRecord(
71+
finding.getId(),
72+
configScopeId,
73+
filePath.toString(),
74+
finding.getServerKey(),
75+
finding.getRuleKey(),
76+
finding.getMessage(),
77+
introductionDate,
78+
type.name(),
79+
textRangeWithHash == null ? null : textRangeWithHash.getStartLine(),
80+
textRangeWithHash == null ? null : textRangeWithHash.getStartLineOffset(),
81+
textRangeWithHash == null ? null : textRangeWithHash.getEndLine(),
82+
textRangeWithHash == null ? null : textRangeWithHash.getEndLineOffset(),
83+
textRangeWithHash == null ? null : textRangeWithHash.getHash(),
84+
lineWithHash == null ? null : lineWithHash.getNumber(),
85+
lineWithHash == null ? null : lineWithHash.getHash());
86+
}
87+
4488
public void storeKnownIssues(String configurationScopeId, Path clientRelativePath, List<KnownFinding> newKnownIssues) {
4589
storeKnownFindings(configurationScopeId, clientRelativePath, newKnownIssues, KnownFindingType.ISSUE);
4690
}
@@ -69,7 +113,7 @@ private void storeKnownFindings(String configurationScopeId, Path clientRelative
69113
var lineWithHash = finding.getLineWithHash();
70114
var line = lineWithHash == null ? null : lineWithHash.getNumber();
71115
var lineHash = lineWithHash == null ? null : lineWithHash.getHash();
72-
var introDate = LocalDateTime.ofInstant(finding.getIntroductionDate(), ZoneId.systemDefault());
116+
var introDate = LocalDateTime.ofInstant(finding.getIntroductionDate(), ZoneOffset.UTC);
73117
trx.dsl().mergeInto(KNOWN_FINDINGS)
74118
.using(trx.dsl().selectOne())
75119
.on(KNOWN_FINDINGS.ID.eq(finding.getId()))
@@ -95,8 +139,7 @@ private void storeKnownFindings(String configurationScopeId, Path clientRelative
95139
.values(finding.getId(), configurationScopeId, clientRelativePath.toString(), finding.getServerKey(), finding.getRuleKey(),
96140
finding.getMessage(), introDate, type.name(),
97141
startLine, startLineOffset, endLine, endLineOffset, textRangeHash,
98-
line, lineHash
99-
)
142+
line, lineHash)
100143
.execute();
101144
}));
102145
}
@@ -106,8 +149,7 @@ private List<KnownFinding> getKnownFindingsForFile(String configurationScopeId,
106149
.selectFrom(KNOWN_FINDINGS)
107150
.where(KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID.eq(configurationScopeId)
108151
.and(KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH.eq(filePath.toString()))
109-
.and(KNOWN_FINDINGS.FINDING_TYPE.eq(type.name()))
110-
)
152+
.and(KNOWN_FINDINGS.FINDING_TYPE.eq(type.name())))
111153
.fetch();
112154
return issuesInFile.stream()
113155
.map(KnownFindingsRepository::recordToKnownFinding)
@@ -116,7 +158,7 @@ private List<KnownFinding> getKnownFindingsForFile(String configurationScopeId,
116158

117159
private static KnownFinding recordToKnownFinding(Record rec) {
118160
var id = rec.get(KNOWN_FINDINGS.ID);
119-
var introductionDate = rec.get(KNOWN_FINDINGS.INTRODUCTION_DATE).atZone(ZoneId.systemDefault()).toInstant();
161+
var introductionDate = rec.get(KNOWN_FINDINGS.INTRODUCTION_DATE).toInstant(ZoneOffset.UTC);
120162
var textRangeWithHash = getTextRangeWithHash(rec);
121163
var lineWithHash = getLineWithHash(rec);
122164
return new KnownFinding(
@@ -125,25 +167,27 @@ private static KnownFinding recordToKnownFinding(Record rec) {
125167
textRangeWithHash, lineWithHash,
126168
rec.get(KNOWN_FINDINGS.RULE_KEY),
127169
rec.get(KNOWN_FINDINGS.MESSAGE),
128-
introductionDate
129-
);
170+
introductionDate);
130171
}
131172

132173
private static LineWithHash getLineWithHash(Record rec) {
133-
if (rec.get(KNOWN_FINDINGS.LINE) == null) return null;
134174
var line = rec.get(KNOWN_FINDINGS.LINE);
175+
if (line == null) {
176+
return null;
177+
}
135178
var hash = rec.get(KNOWN_FINDINGS.LINE_HASH);
136179
return new LineWithHash(line, hash);
137180
}
138181

139182
private static TextRangeWithHash getTextRangeWithHash(Record rec) {
140-
if (rec.get(KNOWN_FINDINGS.START_LINE) == null) return null;
141183
var startLine = rec.get(KNOWN_FINDINGS.START_LINE);
184+
if (startLine == null) {
185+
return null;
186+
}
142187
var endLine = rec.get(KNOWN_FINDINGS.END_LINE);
143188
var startLineOffset = rec.get(KNOWN_FINDINGS.START_LINE_OFFSET);
144189
var endLineOffset = rec.get(KNOWN_FINDINGS.END_LINE_OFFSET);
145190
var hash = rec.get(KNOWN_FINDINGS.TEXT_RANGE_HASH);
146191
return new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, hash);
147192
}
148-
149193
}

backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/KnownFindingsStorageService.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@
2020
package org.sonarsource.sonarlint.core.tracking;
2121

2222
import java.io.IOException;
23+
import java.nio.file.Files;
2324
import java.nio.file.Path;
2425
import java.util.concurrent.atomic.AtomicReference;
2526
import javax.annotation.PreDestroy;
27+
import org.apache.commons.io.FileUtils;
2628
import org.sonarsource.sonarlint.core.UserPaths;
2729

30+
import static org.sonarsource.sonarlint.core.commons.storage.XodusPurgeUtils.deleteInFolderWithPattern;
31+
import static org.sonarsource.sonarlint.core.tracking.XodusKnownFindingsStore.BACKUP_TAR_GZ;
32+
import static org.sonarsource.sonarlint.core.tracking.XodusKnownFindingsStore.KNOWN_FINDINGS_STORE;
33+
2834
public class KnownFindingsStorageService {
2935

3036
private final Path projectsStorageBaseDir;
@@ -36,6 +42,10 @@ public KnownFindingsStorageService(UserPaths userPaths) {
3642
this.workDir = userPaths.getWorkDir();
3743
}
3844

45+
public boolean exists() {
46+
return Files.exists(projectsStorageBaseDir.resolve(XodusKnownFindingsStore.BACKUP_TAR_GZ));
47+
}
48+
3949
public synchronized XodusKnownFindingsStore get() {
4050
var store = trackedIssuesStore.get();
4151
if (store == null) {
@@ -54,8 +64,17 @@ public synchronized XodusKnownFindingsStore get() {
5464
public void close() {
5565
var store = trackedIssuesStore.get();
5666
if (store != null) {
57-
store.close();
67+
store.backupAndClose();
5868
}
5969
}
6070

71+
public void delete() {
72+
var store = trackedIssuesStore.getAndSet(null);
73+
if (store != null) {
74+
store.close();
75+
}
76+
FileUtils.deleteQuietly(projectsStorageBaseDir.resolve(BACKUP_TAR_GZ).toFile());
77+
deleteInFolderWithPattern(workDir, KNOWN_FINDINGS_STORE + "*");
78+
deleteInFolderWithPattern(projectsStorageBaseDir, "known_findings_backup*");
79+
}
6180
}

backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackingService.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.sonarsource.sonarlint.core.tracking;
2121

22+
import jakarta.annotation.PostConstruct;
2223
import java.net.URI;
2324
import java.nio.file.Path;
2425
import java.time.Instant;
@@ -117,6 +118,24 @@ public TrackingService(SonarLintRpcClient client, ConfigurationRepository config
117118
this.databaseService = databaseService;
118119
}
119120

121+
@PostConstruct
122+
public void migrateData() {
123+
if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) {
124+
if (knownFindingsStorageService.exists()) {
125+
try {
126+
var repository = new KnownFindingsRepository(databaseService.getDatabase());
127+
var xodusKnownFindingsStore = knownFindingsStorageService.get();
128+
var findingsPerConfigScope = xodusKnownFindingsStore.loadAll();
129+
repository.storeFindings(findingsPerConfigScope);
130+
} catch (Exception e) {
131+
LOG.error("Unable to migrate known findings, will use fresh DB", e);
132+
}
133+
}
134+
// always call to remove lingering temporary files
135+
knownFindingsStorageService.delete();
136+
}
137+
}
138+
120139
@EventListener
121140
public void onAnalysisStarted(AnalysisStartedEvent event) {
122141
var configurationScopeId = event.getConfigurationScopeId();
@@ -168,7 +187,6 @@ private MatchingResult matchWithServerFindings(String configurationScopeId, Matc
168187
var effectiveBindingOpt = configurationRepository.getEffectiveBinding(configurationScopeId);
169188
var activeBranchOpt = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);
170189
var translationOpt = pathTranslationService.getOrComputePathTranslation(configurationScopeId);
171-
var knownFindingsStore = knownFindingsStorageService.get();
172190
var issuesToReport = matchingSession.getIssuesPerFile();
173191
var hotspotsToReport = matchingSession.getSecurityHotspotsPerFile();
174192
if (effectiveBindingOpt.isPresent() && activeBranchOpt.isPresent() && translationOpt.isPresent()) {
@@ -191,34 +209,34 @@ private MatchingResult matchWithServerFindings(String configurationScopeId, Matc
191209
return Map.entry(ideRelativePath, matches);
192210
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
193211
}
194-
issuesToReport.forEach((clientRelativePath, trackedIssues) -> storeTrackedIssues(knownFindingsStore, configurationScopeId, clientRelativePath, trackedIssues));
195-
hotspotsToReport.forEach((clientRelativePath, trackedHotspots) -> storeTrackedSecurityHotspots(knownFindingsStore, configurationScopeId, clientRelativePath, trackedHotspots));
212+
issuesToReport.forEach((clientRelativePath, trackedIssues) -> storeTrackedIssues(configurationScopeId, clientRelativePath, trackedIssues));
213+
hotspotsToReport.forEach((clientRelativePath, trackedHotspots) -> storeTrackedSecurityHotspots(configurationScopeId, clientRelativePath, trackedHotspots));
196214
eventPublisher.publishEvent(new MatchingSessionEndedEvent(matchingSession.countNewIssues(), matchingSession.countRemainingUnmatchedIssues()));
197215
return new MatchingResult(issuesToReport, hotspotsToReport);
198216
}
199217

200-
private void storeTrackedIssues(XodusKnownFindingsStore knownIssuesStore, String configurationScopeId, Path clientRelativePath, Collection<TrackedIssue> newKnownIssues) {
218+
private void storeTrackedIssues(String configurationScopeId, Path clientRelativePath, Collection<TrackedIssue> newKnownIssues) {
201219
if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) {
202220
var knownFindingsRepository = new KnownFindingsRepository(databaseService.getDatabase());
203221
knownFindingsRepository.storeKnownIssues(configurationScopeId, clientRelativePath,
204222
newKnownIssues.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(),
205223
i.getIntroductionDate())).toList());
206224
} else {
207-
knownIssuesStore.storeKnownIssues(configurationScopeId, clientRelativePath,
225+
knownFindingsStorageService.get().storeKnownIssues(configurationScopeId, clientRelativePath,
208226
newKnownIssues.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(),
209227
i.getIntroductionDate())).toList());
210228
}
211229
}
212230

213-
private void storeTrackedSecurityHotspots(XodusKnownFindingsStore knownIssuesStore, String configurationScopeId, Path clientRelativePath,
231+
private void storeTrackedSecurityHotspots(String configurationScopeId, Path clientRelativePath,
214232
Collection<TrackedIssue> newKnownSecurityHotspots) {
215233
if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) {
216234
var knownFindingsRepository = new KnownFindingsRepository(databaseService.getDatabase());
217235
knownFindingsRepository.storeKnownSecurityHotspots(configurationScopeId, clientRelativePath,
218236
newKnownSecurityHotspots.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(),
219237
i.getIntroductionDate())).toList());
220238
} else {
221-
knownIssuesStore.storeKnownSecurityHotspots(configurationScopeId, clientRelativePath,
239+
knownFindingsStorageService.get().storeKnownSecurityHotspots(configurationScopeId, clientRelativePath,
222240
newKnownSecurityHotspots.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(),
223241
i.getIntroductionDate())).toList());
224242
}
@@ -283,7 +301,7 @@ private static TrackedIssue updateRawHotspotWithServerData(TrackedIssue trackedH
283301
serverHotspot.getStatus().isResolved(), trackedHotspot.getSeverity(), RuleType.SECURITY_HOTSPOT, trackedHotspot.getRuleKey(),
284302
trackedHotspot.getTextRangeWithHash(), trackedHotspot.getLineWithHash(),
285303
serverHotspot.getKey(), trackedHotspot.getImpacts(), trackedHotspot.getFlows(), trackedHotspot.getQuickFixes(),
286-
serverHotspot.getVulnerabilityProbability(), HotspotStatus.valueOf(serverHotspot.getStatus().name()), null, trackedHotspot.getRuleDescriptionContextKey(),
304+
serverHotspot.getVulnerabilityProbability(), HotspotStatus.valueOf(serverHotspot.getStatus().name()), null, trackedHotspot.getRuleDescriptionContextKey(),
287305
trackedHotspot.getCleanCodeAttribute(), trackedHotspot.getFileUri());
288306
}
289307

@@ -301,7 +319,6 @@ private static TrackedIssue updateTrackedIssueWithLocalOnlyIssueData(TrackedIssu
301319
}
302320

303321
private MatchingSession startMatchingSession(String configurationScopeId, Set<Path> fileRelativePaths, Set<URI> fileUris, UnaryOperator<String> fileContentProvider) {
304-
var knownFindingsStore = knownFindingsStorageService.get();
305322
var dogfoodEnvironment = dogfoodEnvironmentDetectionService.isDogfoodEnvironment();
306323
Map<Path, List<KnownFinding>> issuesByRelativePath;
307324
Map<Path, List<KnownFinding>> hotspotsByRelativePath;
@@ -312,6 +329,7 @@ private MatchingSession startMatchingSession(String configurationScopeId, Set<Pa
312329
hotspotsByRelativePath = fileRelativePaths.stream()
313330
.collect(toMap(Function.identity(), relativePath -> knownFindingsRepository.loadSecurityHotspotsForFile(configurationScopeId, relativePath)));
314331
} else {
332+
var knownFindingsStore = knownFindingsStorageService.get();
315333
issuesByRelativePath = fileRelativePaths.stream()
316334
.collect(toMap(Function.identity(), relativePath -> knownFindingsStore.loadIssuesForFile(configurationScopeId, relativePath)));
317335
hotspotsByRelativePath = fileRelativePaths.stream()

0 commit comments

Comments
 (0)