From 06919fe62a5f9b7a0e7a0ed77b52b80b8f87394f Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 21 Jun 2016 12:08:02 +0200 Subject: [PATCH] SONAR-7689 support DUPLICATIONS_INDEX.ANALYSIS_UUID in code --- .../server/computation/snapshot/Snapshot.java | 20 ++- .../step/BuildComponentTreeStep.java | 1 + ...rossProjectDuplicationsRepositoryStep.java | 4 +- ...rsistCrossProjectDuplicationIndexStep.java | 33 ++-- .../AnalysisMetadataHolderImplTest.java | 1 + .../filemove/FileMoveDetectionStepTest.java | 5 +- .../scm/ScmInfoRepositoryImplTest.java | 1 + .../snapshot/SnapshotImplTest.java | 29 +++- ...ProjectDuplicationsRepositoryStepTest.java | 13 +- ...tCrossProjectDuplicationIndexStepTest.java | 56 +++---- .../sonar/db/duplication/DuplicationDao.java | 6 +- .../db/duplication/DuplicationMapper.java | 2 +- .../db/duplication/DuplicationUnitDto.java | 20 +-- .../java/org/sonar/db/purge/IdUuidPairs.java | 32 ++-- .../org/sonar/db/purge/PurgeCommands.java | 38 ++--- .../java/org/sonar/db/purge/PurgeMapper.java | 6 +- .../db/duplication/DuplicationMapper.xml | 19 ++- .../org/sonar/db/purge/PurgeMapper.xml | 14 +- .../db/duplication/DuplicationDaoTest.java | 8 +- .../org/sonar/db/purge/PurgeCommandsTest.java | 10 +- .../DuplicationDaoTest/insert-result.xml | 3 +- .../DuplicationDaoTest/select_candidates.xml | 141 ++++++++++++++---- .../shouldDeleteSnapshot-result.xml | 66 +++++--- .../shouldDeleteSnapshot.xml | 133 ++++++++++++----- .../shouldPurgeSnapshot-result.xml | 127 ++++++++++++---- .../PurgeCommandsTest/shouldPurgeSnapshot.xml | 133 ++++++++++++----- 26 files changed, 628 insertions(+), 293 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/snapshot/Snapshot.java b/server/sonar-server/src/main/java/org/sonar/server/computation/snapshot/Snapshot.java index b7db3767cee..28ae303b75d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/snapshot/Snapshot.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/snapshot/Snapshot.java @@ -20,6 +20,7 @@ package org.sonar.server.computation.snapshot; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import static com.google.common.base.Preconditions.checkNotNull; @@ -28,10 +29,12 @@ import static com.google.common.base.Preconditions.checkNotNull; public class Snapshot { private long id; + private String uuid; private long createdAt; private Snapshot(Builder builder) { this.id = builder.id; + this.uuid = builder.uuid; this.createdAt = builder.createdAt; } @@ -39,6 +42,10 @@ public class Snapshot { return id; } + public String getUuid() { + return uuid; + } + public long getCreatedAt() { return createdAt; } @@ -47,6 +54,8 @@ public class Snapshot { @CheckForNull private Long id; @CheckForNull + private String uuid; + @CheckForNull private Long createdAt; public Builder setId(long id) { @@ -54,6 +63,11 @@ public class Snapshot { return this; } + public Builder setUuid(String uuid) { + this.uuid = uuid; + return this; + } + public Builder setCreatedAt(long createdAt) { this.createdAt = createdAt; return this; @@ -61,13 +75,14 @@ public class Snapshot { public Snapshot build() { checkNotNull(id, "id cannot be null"); + checkNotNull(uuid, "uuid cannot be null"); checkNotNull(createdAt, "createdAt cannot be null"); return new Snapshot(this); } } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } @@ -86,8 +101,9 @@ public class Snapshot { @Override public String toString() { - return "SnapshotImpl{" + + return "Snapshot{" + "id=" + id + + ", uuid='" + uuid + '\'' + ", createdAt=" + createdAt + '}'; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/BuildComponentTreeStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/BuildComponentTreeStep.java index 1805581d6fb..c0dfa740a93 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/BuildComponentTreeStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/BuildComponentTreeStep.java @@ -83,6 +83,7 @@ public class BuildComponentTreeStep implements ComputationStep { private static Snapshot toSnapshot(@Nullable SnapshotDto snapshotDto) { return snapshotDto == null ? null : new Snapshot.Builder() .setId(snapshotDto.getId()) + .setUuid(snapshotDto.getUuid()) .setCreatedAt(snapshotDto.getCreatedAt()) .build(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStep.java index addd61f5fab..11e8860d04a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStep.java @@ -118,8 +118,8 @@ public class LoadCrossProjectDuplicationsRepositoryStep implements ComputationSt DbSession dbSession = dbClient.openSession(false); try { Snapshot projectSnapshot = analysisMetadataHolder.getBaseProjectSnapshot(); - Long projectSnapshotId = projectSnapshot == null ? null : projectSnapshot.getId(); - return dbClient.duplicationDao().selectCandidates(dbSession, projectSnapshotId, file.getFileAttributes().getLanguageKey(), hashes); + String analysisUuid = projectSnapshot == null ? null : projectSnapshot.getUuid(); + return dbClient.duplicationDao().selectCandidates(dbSession, analysisUuid, file.getFileAttributes().getLanguageKey(), hashes); } finally { dbClient.closeSession(dbSession); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStep.java index a59c2af1306..5ed6136e9c6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStep.java @@ -24,10 +24,10 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.duplication.DuplicationUnitDto; import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.server.computation.analysis.AnalysisMetadataHolder; import org.sonar.server.computation.batch.BatchReportReader; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.CrawlerDepthLimit; -import org.sonar.server.computation.component.DbIdsRepository; import org.sonar.server.computation.component.DepthTraversalTypeAwareCrawler; import org.sonar.server.computation.component.TreeRootHolder; import org.sonar.server.computation.component.TypeAwareVisitorAdapter; @@ -42,28 +42,30 @@ public class PersistCrossProjectDuplicationIndexStep implements ComputationStep private final DbClient dbClient; private final TreeRootHolder treeRootHolder; + private final AnalysisMetadataHolder analysisMetadataHolder; private final BatchReportReader reportReader; - private final DbIdsRepository dbIdsRepository; private final CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder; - public PersistCrossProjectDuplicationIndexStep(DbClient dbClient, DbIdsRepository dbIdsRepository, TreeRootHolder treeRootHolder, BatchReportReader reportReader, - CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder) { + public PersistCrossProjectDuplicationIndexStep(CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder, DbClient dbClient, + TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder, + BatchReportReader reportReader) { this.dbClient = dbClient; this.treeRootHolder = treeRootHolder; + this.analysisMetadataHolder = analysisMetadataHolder; this.reportReader = reportReader; - this.dbIdsRepository = dbIdsRepository; this.crossProjectDuplicationStatusHolder = crossProjectDuplicationStatusHolder; } @Override public void execute() { + if (!crossProjectDuplicationStatusHolder.isEnabled()) { + return; + } + DbSession session = dbClient.openSession(true); try { - if (crossProjectDuplicationStatusHolder.isEnabled()) { - Component project = treeRootHolder.getRoot(); - long projectSnapshotId = dbIdsRepository.getSnapshotId(project); - new DepthTraversalTypeAwareCrawler(new DuplicationVisitor(session, projectSnapshotId)).visit(project); - } + Component project = treeRootHolder.getRoot(); + new DepthTraversalTypeAwareCrawler(new DuplicationVisitor(session, analysisMetadataHolder.getUuid())).visit(project); session.commit(); } finally { dbClient.closeSession(session); @@ -73,12 +75,12 @@ public class PersistCrossProjectDuplicationIndexStep implements ComputationStep private class DuplicationVisitor extends TypeAwareVisitorAdapter { private final DbSession session; - private final long projectSnapshotId; + private final String analysisUuid; - private DuplicationVisitor(DbSession session, long projectSnapshotId) { + private DuplicationVisitor(DbSession session, String analysisUuid) { super(CrawlerDepthLimit.FILE, PRE_ORDER); this.session = session; - this.projectSnapshotId = projectSnapshotId; + this.analysisUuid = analysisUuid; } @Override @@ -99,9 +101,8 @@ public class PersistCrossProjectDuplicationIndexStep implements ComputationStep .setStartLine(block.getStartLine()) .setEndLine(block.getEndLine()) .setIndexInFile(indexInFile) - .setSnapshotId(dbIdsRepository.getSnapshotId(component)) - .setComponentUuid(component.getUuid()) - .setProjectSnapshotId(projectSnapshotId)); + .setAnalysisUuid(analysisUuid) + .setComponentUuid(component.getUuid())); indexInFile++; } } finally { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderImplTest.java index 7dbe09306e5..029815d10e5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderImplTest.java @@ -33,6 +33,7 @@ public class AnalysisMetadataHolderImplTest { static Snapshot BASE_PROJECT_SNAPSHOT = new Snapshot.Builder() .setId(1) + .setUuid("uuid_1") .setCreatedAt(123456789L) .build(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/filemove/FileMoveDetectionStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/filemove/FileMoveDetectionStepTest.java index a2dd17509b9..3d2b80830f5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/filemove/FileMoveDetectionStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/filemove/FileMoveDetectionStepTest.java @@ -58,6 +58,7 @@ public class FileMoveDetectionStepTest { private static final long SNAPSHOT_ID = 98765; private static final Snapshot SNAPSHOT = new Snapshot.Builder() .setId(SNAPSHOT_ID) + .setUuid("uuid_1") .setCreatedAt(86521) .build(); private static final int ROOT_REF = 1; @@ -400,11 +401,11 @@ public class FileMoveDetectionStepTest { ComponentDtoWithSnapshotId[] dtos = mockComponentsForSnapshot(FILE_1.getKey(), FILE_2.getKey(), file4.getKey(), file5.getKey()); mockContentOfFileIdDb(FILE_1.getKey(), CONTENT1); mockContentOfFileIdDb(FILE_2.getKey(), LESS_CONTENT1); - mockContentOfFileIdDb(file4.getKey(), new String[]{"e","f","g","h","i"}); + mockContentOfFileIdDb(file4.getKey(), new String[] {"e", "f", "g", "h", "i"}); mockContentOfFileIdDb(file5.getKey(), CONTENT2); setFilesInReport(FILE_3, file4, file6); setFileContentInReport(FILE_3_REF, CONTENT1); - setFileContentInReport(file4.getReportAttributes().getRef(), new String[]{"a","b"}); + setFileContentInReport(file4.getReportAttributes().getRef(), new String[] {"a", "b"}); setFileContentInReport(file6.getReportAttributes().getRef(), LESS_CONTENT2); underTest.execute(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java index 3c8993cf2f0..85eb2112923 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java @@ -68,6 +68,7 @@ public class ScmInfoRepositoryImplTest { static Snapshot BASE_PROJECT_SNAPSHOT = new Snapshot.Builder() .setId(1) + .setUuid("uuid_1") .setCreatedAt(123456789L) .build(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/snapshot/SnapshotImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/snapshot/SnapshotImplTest.java index 0ba8a38278f..51420bd87d3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/snapshot/SnapshotImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/snapshot/SnapshotImplTest.java @@ -31,16 +31,19 @@ public class SnapshotImplTest { public ExpectedException thrown = ExpectedException.none(); static final long ID = 10; + static final String UUID = "uuid "; static final long CREATED_AT = 123456789L; @Test public void build_snapshot() throws Exception { Snapshot snapshot = new Snapshot.Builder() .setId(ID) + .setUuid(UUID) .setCreatedAt(CREATED_AT) .build(); assertThat(snapshot.getId()).isEqualTo(ID); + assertThat(snapshot.getUuid()).isEqualTo(UUID); assertThat(snapshot.getCreatedAt()).isEqualTo(CREATED_AT); } @@ -50,6 +53,18 @@ public class SnapshotImplTest { thrown.expectMessage("id cannot be null"); new Snapshot.Builder() + .setUuid(UUID) + .setCreatedAt(CREATED_AT) + .build(); + } + + @Test + public void fail_with_NPE_when_building_snapshot_without_uuid() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("uuid cannot be null"); + + new Snapshot.Builder() + .setId(ID) .setCreatedAt(CREATED_AT) .build(); } @@ -61,6 +76,7 @@ public class SnapshotImplTest { new Snapshot.Builder() .setId(ID) + .setUuid(UUID) .build(); } @@ -68,33 +84,44 @@ public class SnapshotImplTest { public void test_toString() throws Exception { assertThat(new Snapshot.Builder() .setId(ID) + .setUuid(UUID) .setCreatedAt(CREATED_AT) .build().toString()) - .isEqualTo("SnapshotImpl{id=10, createdAt=123456789}"); + .isEqualTo("Snapshot{id=10, uuid='uuid ', createdAt=123456789}"); } @Test public void test_equals_and_hascode() throws Exception { Snapshot snapshot = new Snapshot.Builder() .setId(ID) + .setUuid(UUID) .setCreatedAt(CREATED_AT) .build(); Snapshot sameSnapshot = new Snapshot.Builder() .setId(ID) + .setUuid(UUID) + .setCreatedAt(CREATED_AT) + .build(); + Snapshot sameSnapshotNotSameUuid = new Snapshot.Builder() + .setId(ID) + .setUuid("other uuid") .setCreatedAt(CREATED_AT) .build(); Snapshot otherSnapshot = new Snapshot.Builder() .setId(11L) + .setUuid(UUID) .setCreatedAt(CREATED_AT) .build(); assertThat(snapshot).isEqualTo(snapshot); assertThat(snapshot).isEqualTo(sameSnapshot); + assertThat(snapshot).isEqualTo(sameSnapshotNotSameUuid); assertThat(snapshot).isNotEqualTo(otherSnapshot); assertThat(snapshot).isNotEqualTo(null); assertThat(snapshot.hashCode()).isEqualTo(snapshot.hashCode()); assertThat(snapshot.hashCode()).isEqualTo(sameSnapshot.hashCode()); + assertThat(snapshot.hashCode()).isEqualTo(sameSnapshotNotSameUuid.hashCode()); assertThat(snapshot.hashCode()).isNotEqualTo(otherSnapshot.hashCode()); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStepTest.java index 1b84b4591a9..2ae008a2c4a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadCrossProjectDuplicationsRepositoryStepTest.java @@ -115,6 +115,7 @@ public class LoadCrossProjectDuplicationsRepositoryStepTest { baseProjectSnapshot = new Snapshot.Builder() .setId(projectSnapshot.getId()) + .setUuid(projectSnapshot.getUuid()) .setCreatedAt(projectSnapshot.getCreatedAt()) .build(); } @@ -136,8 +137,7 @@ public class LoadCrossProjectDuplicationsRepositoryStepTest { .setStartLine(40) .setEndLine(55) .setIndexInFile(0) - .setProjectSnapshotId(otherProjectSnapshot.getId()) - .setSnapshotId(otherFileSnapshot.getId()) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) .setComponentUuid(otherFileSnapshot.getComponentUuid()); dbClient.duplicationDao().insert(dbSession, duplicate); dbSession.commit(); @@ -203,8 +203,7 @@ public class LoadCrossProjectDuplicationsRepositoryStepTest { .setStartLine(40) .setEndLine(55) .setIndexInFile(0) - .setProjectSnapshotId(otherProjectSnapshot.getId()) - .setSnapshotId(otherFileSnapshot.getId()) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) .setComponentUuid(otherFileSnapshot.getComponentUuid()); DuplicationUnitDto duplicate2 = new DuplicationUnitDto() @@ -212,8 +211,7 @@ public class LoadCrossProjectDuplicationsRepositoryStepTest { .setStartLine(20) .setEndLine(35) .setIndexInFile(1) - .setProjectSnapshotId(otherProjectSnapshot.getId()) - .setSnapshotId(otherFileSnapshot.getId()) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) .setComponentUuid(otherFileSnapshot.getComponentUuid()); dbClient.duplicationDao().insert(dbSession, duplicate1); dbClient.duplicationDao().insert(dbSession, duplicate2); @@ -279,8 +277,7 @@ public class LoadCrossProjectDuplicationsRepositoryStepTest { .setStartLine(40) .setEndLine(55) .setIndexInFile(0) - .setProjectSnapshotId(otherProjectSnapshot.getId()) - .setSnapshotId(otherFileSnapshot.getId()) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) .setComponentUuid(otherFileSnapshot.getComponentUuid()); dbClient.duplicationDao().insert(dbSession, duplicate); dbSession.commit(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStepTest.java index 6fcabab19e8..455ba0fc6eb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistCrossProjectDuplicationIndexStepTest.java @@ -30,10 +30,10 @@ import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbTester; import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.server.computation.analysis.AnalysisMetadataHolderRule; import org.sonar.server.computation.batch.BatchReportReaderRule; import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; -import org.sonar.server.computation.component.DbIdsRepositoryImpl; import org.sonar.server.computation.component.ReportComponent; import org.sonar.server.computation.duplication.CrossProjectDuplicationStatusHolder; @@ -44,42 +44,38 @@ import static org.mockito.Mockito.when; public class PersistCrossProjectDuplicationIndexStepTest { - static final int FILE_REF = 2; - static final Component FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).build(); - static final long FILE_SNAPSHOT_ID = 11L; + private static final int FILE_REF = 2; + private static final Component FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).build(); - static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1) + private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1) .addChildren(FILE) .build(); - static final long PROJECT_SNAPSHOT_ID = 10L; - static final ScannerReport.CpdTextBlock CPD_TEXT_BLOCK = ScannerReport.CpdTextBlock.newBuilder() + private static final ScannerReport.CpdTextBlock CPD_TEXT_BLOCK = ScannerReport.CpdTextBlock.newBuilder() .setHash("a8998353e96320ec") .setStartLine(30) .setEndLine(45) .build(); + private static final String ANALYSIS_UUID = "analysis uuid"; @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - @Rule public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT); + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class); - DbIdsRepositoryImpl dbIdsRepository = new DbIdsRepositoryImpl(); - DbClient dbClient = dbTester.getDbClient(); - ComputationStep underTest = new PersistCrossProjectDuplicationIndexStep(dbClient, dbIdsRepository, treeRootHolder, reportReader, crossProjectDuplicationStatusHolder); + ComputationStep underTest = new PersistCrossProjectDuplicationIndexStep(crossProjectDuplicationStatusHolder, dbClient, treeRootHolder, analysisMetadataHolder, reportReader); @Before public void setUp() throws Exception { - dbIdsRepository.setSnapshotId(PROJECT, PROJECT_SNAPSHOT_ID); - dbIdsRepository.setSnapshotId(FILE, FILE_SNAPSHOT_ID); + analysisMetadataHolder.setUuid(ANALYSIS_UUID); } @Test @@ -89,15 +85,13 @@ public class PersistCrossProjectDuplicationIndexStepTest { underTest.execute(); - Map dto = dbTester.selectFirst("select hash as \"hash\", start_line as \"startLine\", end_line as \"endLine\", index_in_file as \"indexInFile\", " + - "snapshot_id as \"snapshotId\", component_uuid as \"componentUuid\", project_snapshot_id as \"projectSnapshotId\" from duplications_index"); - assertThat(dto.get("hash")).isEqualTo(CPD_TEXT_BLOCK.getHash()); - assertThat(dto.get("startLine")).isEqualTo(30L); - assertThat(dto.get("endLine")).isEqualTo(45L); - assertThat(dto.get("indexInFile")).isEqualTo(0L); - assertThat(dto.get("snapshotId")).isEqualTo(FILE_SNAPSHOT_ID); - assertThat(dto.get("componentUuid")).isEqualTo(FILE.getUuid()); - assertThat(dto.get("projectSnapshotId")).isEqualTo(PROJECT_SNAPSHOT_ID); + Map dto = dbTester.selectFirst("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index"); + assertThat(dto.get("HASH")).isEqualTo(CPD_TEXT_BLOCK.getHash()); + assertThat(dto.get("START_LINE")).isEqualTo(30L); + assertThat(dto.get("END_LINE")).isEqualTo(45L); + assertThat(dto.get("INDEX_IN_FILE")).isEqualTo(0L); + assertThat(dto.get("COMPONENT_UUID")).isEqualTo(FILE.getUuid()); + assertThat(dto.get("ANALYSIS_UUID")).isEqualTo(ANALYSIS_UUID); } @Test @@ -113,15 +107,13 @@ public class PersistCrossProjectDuplicationIndexStepTest { underTest.execute(); - List> dtos = dbTester.select("select hash as \"hash\", start_line as \"startLine\", end_line as \"endLine\", index_in_file as \"indexInFile\", " + - "snapshot_id as \"snapshotId\", component_uuid as \"componentUuid\", project_snapshot_id as \"projectSnapshotId\" from duplications_index"); - assertThat(dtos).extracting("hash").containsOnly(CPD_TEXT_BLOCK.getHash(), "b1234353e96320ff"); - assertThat(dtos).extracting("startLine").containsOnly(30L, 20L); - assertThat(dtos).extracting("endLine").containsOnly(45L, 15L); - assertThat(dtos).extracting("indexInFile").containsOnly(0L, 1L); - assertThat(dtos).extracting("snapshotId").containsOnly(FILE_SNAPSHOT_ID); - assertThat(dtos).extracting("componentUuid").containsOnly(FILE.getUuid()); - assertThat(dtos).extracting("projectSnapshotId").containsOnly(PROJECT_SNAPSHOT_ID); + List> dtos = dbTester.select("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index"); + assertThat(dtos).extracting("HASH").containsOnly(CPD_TEXT_BLOCK.getHash(), "b1234353e96320ff"); + assertThat(dtos).extracting("START_LINE").containsOnly(30L, 20L); + assertThat(dtos).extracting("END_LINE").containsOnly(45L, 15L); + assertThat(dtos).extracting("INDEX_IN_FILE").containsOnly(0L, 1L); + assertThat(dtos).extracting("COMPONENT_UUID").containsOnly(FILE.getUuid()); + assertThat(dtos).extracting("ANALYSIS_UUID").containsOnly(ANALYSIS_UUID); } @Test diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java index a9c5935ace9..b4b5a11ed22 100644 --- a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java +++ b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java @@ -30,12 +30,12 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class DuplicationDao implements Dao { /** - * @param projectSnapshotId snapshot id of the project from the previous analysis (islast=true) + * @param analysisUuid snapshot id of the project from the previous analysis (islast=true) */ - public List selectCandidates(DbSession session, @Nullable Long projectSnapshotId, String language, Collection hashes) { + public List selectCandidates(DbSession session, @Nullable String analysisUuid, String language, Collection hashes) { return executeLargeInputs( hashes, - partition -> session.getMapper(DuplicationMapper.class).selectCandidates(projectSnapshotId, language, partition)); + partition -> session.getMapper(DuplicationMapper.class).selectCandidates(analysisUuid, language, partition)); } /** diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java index cef8e1db6e8..2a1a862f781 100644 --- a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java @@ -27,7 +27,7 @@ import org.apache.ibatis.annotations.Param; public interface DuplicationMapper { List selectCandidates( - @Nullable @Param("projectSnapshotId") Long projectSnapshotId, + @Nullable @Param("analysisUuid") String analysisUuid, @Param("language") String language, @Param("hashes") Collection hashes); diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java index d915fb4466a..14bff7a2239 100644 --- a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java +++ b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java @@ -22,9 +22,8 @@ package org.sonar.db.duplication; public final class DuplicationUnitDto { private long id; - private long snapshotId; + private String analysisUuid; private String componentUuid; - private long projectSnapshotId; private String hash; private int indexInFile; @@ -43,12 +42,12 @@ public final class DuplicationUnitDto { return this; } - public long getSnapshotId() { - return snapshotId; + public String getAnalysisUuid() { + return analysisUuid; } - public DuplicationUnitDto setSnapshotId(long snapshotId) { - this.snapshotId = snapshotId; + public DuplicationUnitDto setAnalysisUuid(String analysisUuid) { + this.analysisUuid = analysisUuid; return this; } @@ -61,15 +60,6 @@ public final class DuplicationUnitDto { return this; } - public long getProjectSnapshotId() { - return projectSnapshotId; - } - - public DuplicationUnitDto setProjectSnapshotId(long projectSnapshotId) { - this.projectSnapshotId = projectSnapshotId; - return this; - } - public String getHash() { return hash; } diff --git a/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java b/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java index 41695fe834b..2e81cecfe9d 100644 --- a/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java +++ b/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java @@ -19,34 +19,36 @@ */ package org.sonar.db.purge; -import com.google.common.base.Function; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; -import javax.annotation.Nonnull; +import java.util.stream.Collectors; -public class IdUuidPairs { +public final class IdUuidPairs { private IdUuidPairs() { + // prevents instantiation } public static List ids(List pairs) { - return Lists.transform(pairs, new IdUuidPairToIdFunction()); + return pairs.stream().map(IdUuidPair::getId).collect(Collectors.toCollection(() -> new ArrayList<>(pairs.size()))); } - public static List uuids(List pairs) { - return Lists.transform(pairs, new IdUuidPairToUuidFunction()); + public static List ids(Iterable pairs) { + if (pairs instanceof List) { + return ids((List) pairs); + } + return ids(Lists.newArrayList(pairs)); } - private static class IdUuidPairToIdFunction implements Function { - @Override - public Long apply(@Nonnull IdUuidPair pair) { - return pair.getId(); - } + public static List uuids(List pairs) { + return pairs.stream().map(IdUuidPair::getUuid).collect(Collectors.toCollection(() -> new ArrayList<>(pairs.size()))); } - private static class IdUuidPairToUuidFunction implements Function { - @Override - public String apply(@Nonnull IdUuidPair pair) { - return pair.getUuid(); + public static List uuids(Iterable pairs) { + if (pairs instanceof List) { + return uuids((List) pairs); } + return uuids(Lists.newArrayList(pairs)); } + } diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java index 8dd3c515c92..91a0300a537 100644 --- a/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java +++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java @@ -20,11 +20,11 @@ package org.sonar.db.purge; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.LinkedHashSet; import java.util.List; +import java.util.stream.Collectors; import org.apache.ibatis.session.SqlSession; import static com.google.common.collect.FluentIterable.from; @@ -51,7 +51,7 @@ class PurgeCommands { } List selectSnapshotIds(PurgeSnapshotQuery query) { - return purgeMapper.selectSnapshotIds(query); + return purgeMapper.selectSnapshotIdsAndUuids(query).stream().map(IdUuidPair::getId).collect(Collectors.toList()); } void deleteComponents(List componentIdUuids) { @@ -62,7 +62,7 @@ class PurgeCommands { // Batch requests can only relate to the same PreparedStatement. for (List componentUuidPartition : componentUuidsPartitions) { - deleteSnapshots(purgeMapper.selectSnapshotIdsByResource(componentUuidPartition)); + deleteSnapshots(purgeMapper.selectSnapshotIdAndUuidsByResource(componentUuidPartition)); } // possible missing optimization: filter requests according to resource scope @@ -146,18 +146,18 @@ class PurgeCommands { } void deleteSnapshots(PurgeSnapshotQuery... queries) { - List snapshotIds = from(asList(queries)) - .transformAndConcat(purgeMapper::selectSnapshotIds) + List snapshotIds = from(asList(queries)) + .transformAndConcat(purgeMapper::selectSnapshotIdsAndUuids) .toList(); deleteSnapshots(snapshotIds); } @VisibleForTesting - protected void deleteSnapshots(final List snapshotIds) { + protected void deleteSnapshots(List snapshotIds) { + List> snapshotIdsPartition = Lists.partition(IdUuidPairs.ids(snapshotIds), MAX_SNAPSHOTS_PER_QUERY); + List> snapshotUuidsPartition = Lists.partition(IdUuidPairs.uuids(snapshotIds), MAX_SNAPSHOTS_PER_QUERY); - List> snapshotIdsPartition = Lists.partition(snapshotIds, MAX_SNAPSHOTS_PER_QUERY); - - deleteSnapshotDuplications(snapshotIdsPartition); + deleteSnapshotDuplications(snapshotUuidsPartition); profiler.start("deleteSnapshotEvents (events)"); for (List partSnapshotIds : snapshotIdsPartition) { @@ -183,16 +183,18 @@ class PurgeCommands { void purgeSnapshots(PurgeSnapshotQuery... queries) { // use LinkedHashSet to keep order by remove duplicated ids - LinkedHashSet snapshotIds = Sets.newLinkedHashSet(from(asList(queries)).transformAndConcat(purgeMapper::selectSnapshotIds)); + LinkedHashSet snapshotIds = Sets.newLinkedHashSet(from(asList(queries)) + .transformAndConcat(purgeMapper::selectSnapshotIdsAndUuids)); purgeSnapshots(snapshotIds); } @VisibleForTesting - protected void purgeSnapshots(Iterable snapshotIds) { + protected void purgeSnapshots(Iterable snapshotIdUuidPairs) { // note that events are not deleted - Iterable> snapshotIdsPartition = Iterables.partition(snapshotIds, MAX_SNAPSHOTS_PER_QUERY); + List> snapshotIdsPartition = Lists.partition(IdUuidPairs.ids(snapshotIdUuidPairs), MAX_SNAPSHOTS_PER_QUERY); + List> snapshotUuidsPartition = Lists.partition(IdUuidPairs.uuids(snapshotIdUuidPairs), MAX_SNAPSHOTS_PER_QUERY); - deleteSnapshotDuplications(snapshotIdsPartition); + deleteSnapshotDuplications(snapshotUuidsPartition); profiler.start("deleteSnapshotWastedMeasures (project_measures)"); List metricIdsWithoutHistoricalData = purgeMapper.selectMetricIdsWithoutHistoricalData(); @@ -203,17 +205,15 @@ class PurgeCommands { profiler.stop(); profiler.start("updatePurgeStatusToOne (snapshots)"); - for (Long snapshotId : snapshotIds) { - purgeMapper.updatePurgeStatusToOne(snapshotId); - } + snapshotIdUuidPairs.iterator().forEachRemaining(idUuidPair -> purgeMapper.updatePurgeStatusToOne(idUuidPair.getId())); session.commit(); profiler.stop(); } - private void deleteSnapshotDuplications(Iterable> snapshotIdsPartition) { + private void deleteSnapshotDuplications(Iterable> snapshotUuidsPartition) { profiler.start("deleteSnapshotDuplications (duplications_index)"); - for (List partSnapshotIds : snapshotIdsPartition) { - purgeMapper.deleteSnapshotDuplications(partSnapshotIds); + for (List partSnapshotUuids : snapshotUuidsPartition) { + purgeMapper.deleteSnapshotDuplications(partSnapshotUuids); } session.commit(); profiler.stop(); diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java index d0f63fe332b..7c237d06ba8 100644 --- a/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java @@ -25,9 +25,9 @@ import org.apache.ibatis.annotations.Param; public interface PurgeMapper { - List selectSnapshotIds(PurgeSnapshotQuery query); + List selectSnapshotIdsAndUuids(PurgeSnapshotQuery query); - List selectSnapshotIdsByResource(@Param("componentUuids") List componentUuids); + List selectSnapshotIdAndUuidsByResource(@Param("componentUuids") List componentUuids); /** * Returns the list of components of a project from a project_uuid. The project itself is also returned. @@ -36,7 +36,7 @@ public interface PurgeMapper { void deleteSnapshot(@Param("snapshotIds") List snapshotIds); - void deleteSnapshotDuplications(@Param("snapshotIds") List snapshotIds); + void deleteSnapshotDuplications(@Param("analysisUuids") List analysisUuids); void deleteSnapshotEvents(@Param("snapshotIds") List snapshotIds); diff --git a/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml b/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml index ec02d2e7ef8..1597543164e 100644 --- a/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml @@ -6,30 +6,35 @@ - INSERT INTO duplications_index (snapshot_id, component_uuid, project_snapshot_id, hash, index_in_file, start_line, end_line) - VALUES (#{snapshotId}, #{componentUuid}, #{projectSnapshotId}, #{hash}, #{indexInFile}, #{startLine}, #{endLine}) + INSERT INTO duplications_index ( + analysis_uuid, component_uuid, hash, + index_in_file, start_line, end_line + ) + VALUES ( + #{analysisUuid,jdbcType=VARCHAR}, #{componentUuid,jdbcType=VARCHAR}, #{hash,jdbcType=VARCHAR}, + #{indexInFile,jdbcType=INTEGER}, #{startLine,jdbcType=INTEGER}, #{endLine,jdbcType=INTEGER} + ) diff --git a/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml index 8c4cce48c0d..8ed0439ca7c 100644 --- a/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml @@ -3,8 +3,8 @@ - + select s.id as id, s.uuid as uuid from snapshots s and s.islast=#{islast} @@ -48,8 +48,8 @@ - + select s.id as id, s.uuid as uuid from snapshots s s.component_uuid in @@ -94,9 +94,9 @@ - delete from duplications_index where snapshot_id in - - #{snapshotId} + delete from duplications_index where analysis_uuid in + + #{analysisUuid} diff --git a/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java b/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java index 4374de33307..20396bf9020 100644 --- a/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java @@ -19,13 +19,14 @@ */ package org.sonar.db.duplication; -import java.util.List; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.System2; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import java.util.List; + import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +45,7 @@ public class DuplicationDaoTest { db.prepareDbUnit(getClass(), "select_candidates.xml"); dbSession.commit(); - List blocks = dao.selectCandidates(dbSession, 7L, "java", singletonList("aa")); + List blocks = dao.selectCandidates(dbSession, "u7", "java", singletonList("aa")); assertThat(blocks).hasSize(1); DuplicationUnitDto block = blocks.get(0); @@ -66,8 +67,7 @@ public class DuplicationDaoTest { dbSession.commit(); dao.insert(dbSession, new DuplicationUnitDto() - .setProjectSnapshotId(1) - .setSnapshotId(2) + .setAnalysisUuid("u1") .setComponentUuid("uuid_1") .setHash("bb") .setIndexInFile(0) diff --git a/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java b/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java index 1c30f1c9583..00c14336d48 100644 --- a/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java +++ b/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java @@ -53,7 +53,7 @@ public class PurgeCommandsTest { */ @Test public void should_not_fail_when_deleting_huge_number_of_snapshots() { - new PurgeCommands(dbTester.getSession(), profiler).deleteSnapshots(getHugeNumberOfIds()); + new PurgeCommands(dbTester.getSession(), profiler).deleteSnapshots(getHugeNumberOfIdUuidPairs()); // The goal of this test is only to check that the query do no fail, not to check result } @@ -83,7 +83,7 @@ public class PurgeCommandsTest { */ @Test public void should_not_fail_when_purging_huge_number_of_snapshots() { - new PurgeCommands(dbTester.getSession(), profiler).purgeSnapshots(getHugeNumberOfIds()); + new PurgeCommands(dbTester.getSession(), profiler).purgeSnapshots(getHugeNumberOfIdUuidPairs()); // The goal of this test is only to check that the query do no fail, not to check result } @@ -118,10 +118,10 @@ public class PurgeCommandsTest { return hugeNbOfSnapshotIds; } - private List getHugeNumberOfIds() { - List hugeNbOfSnapshotIds = newArrayList(); + private List getHugeNumberOfIdUuidPairs() { + List hugeNbOfSnapshotIds = newArrayList(); for (long i = 0; i < 4500; i++) { - hugeNbOfSnapshotIds.add(i); + hugeNbOfSnapshotIds.add(new IdUuidPair(i, "uuid_" + i)); } return hugeNbOfSnapshotIds; } diff --git a/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/insert-result.xml index 072ce7fd586..085383822a8 100644 --- a/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/insert-result.xml +++ b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/insert-result.xml @@ -15,8 +15,7 @@ + component_uuid="uuid_root_1" + status="P" + islast="[false]" + purge_status="[null]" + root_component_uuid="uuid_root_1"/> - + component_uuid="uuid_1" + status="P" + islast="[false]" + purge_status="[null]" + root_component_uuid="uuid_root_1"/> + + component_uuid="uuid_root_2" + status="P" + islast="[true]" + purge_status="[null]" + root_component_uuid="uuid_root_2"/> - + component_uuid="uuid_2" + status="P" + islast="[true]" + purge_status="[null]" + root_component_uuid="uuid_root_2"/> + + component_uuid="uuid_root_3" + status="P" + islast="[false]" + purge_status="[null]" + root_component_uuid="uuid_root_3"/> - + component_uuid="uuid_3" + status="P" + islast="[false]" + purge_status="[null]" + root_component_uuid="uuid_root_3"/> + + component_uuid="uuid_root_4" + status="P" + islast="[true]" + purge_status="[null]" + root_component_uuid="uuid_root_4"/> - + component_uuid="uuid_4" + status="P" + islast="[true]" + purge_status="[null]" + root_component_uuid="uuid_root_4"/> + + component_uuid="uuid_root_5" + status="U" + islast="[false]" + purge_status="[null]" + root_component_uuid="uuid_root_5"/> - + component_uuid="uuid_5" + status="U" + islast="[false]" + purge_status="[null]" + root_component_uuid="uuid_root_5"/> + - + component_uuid="uuid_6" + purge_status="[null]" + status="P" + islast="[true]" + root_component_uuid="uuid_root_1"/> + + version="[null]" + path="[null]"/> - - + alert_status="[null]" + description="[null]" + measure_data="[null]"/> + + version="[null]" + path="[null]"/> - - + alert_status="[null]" + description="[null]" + measure_data="[null]"/> + + version="[null]" + path="[null]"/> - - + alert_status="[null]" + description="[null]" + measure_data="[null]"/> + + component_uuid="uuid_1" + parent_snapshot_id="[null]" + root_component_uuid="uuid_1" + root_snapshot_id="[null]" + status="P" + islast="[false]" + purge_status="1" + period1_mode="[null]" + period1_param="[null]" + period1_date="[null]" + period2_mode="[null]" + period2_param="[null]" + period2_date="[null]" + period3_mode="[null]" + period3_param="[null]" + period3_date="[null]" + period4_mode="[null]" + period4_param="[null]" + period4_date="[null]" + period5_mode="[null]" + period5_param="[null]" + period5_date="[null]" + depth="[null]" + scope="PRJ" + qualifier="TRK" + created_at="1228222680000" + build_date="1228222680000" + version="[null]" + path="[null]"/> @@ -28,15 +48,27 @@ Note that measures, events and reviews are not deleted. + text_value="[null]" + alert_status="[null]" + description="[null]" + measure_data="[null]"/> - @@ -47,35 +79,66 @@ Note that measures, events and reviews are not deleted. + component_uuid="uuid_2" + parent_snapshot_id="[null]" + root_component_uuid="uuid_2" + root_snapshot_id="[null]" + status="P" + islast="[false]" + purge_status="1" + period1_mode="[null]" + period1_param="[null]" + period1_date="[null]" + period2_mode="[null]" + period2_param="[null]" + period2_date="[null]" + period3_mode="[null]" + period3_param="[null]" + period3_date="[null]" + period4_mode="[null]" + period4_param="[null]" + period4_date="[null]" + period5_mode="[null]" + period5_param="[null]" + period5_date="[null]" + depth="[null]" + scope="PRJ" + qualifier="TRK" + created_at="1228222680000" + build_date="1228222680000" + version="[null]" + path="[null]"/> + text_value="[null]" + alert_status="[null]" + description="[null]" + measure_data="[null]"/> - + component_uuid="uuid_1" + parent_snapshot_id="[null]" + root_component_uuid="uuid_1" + root_snapshot_id="[null]" + status="P" + islast="[false]" + purge_status="[null]" + period1_mode="[null]" + period1_param="[null]" + period1_date="[null]" + period2_mode="[null]" + period2_param="[null]" + period2_date="[null]" + period3_mode="[null]" + period3_param="[null]" + period3_date="[null]" + period4_mode="[null]" + period4_param="[null]" + period4_date="[null]" + period5_mode="[null]" + period5_param="[null]" + period5_date="[null]" + depth="[null]" + scope="PRJ" + qualifier="TRK" + created_at="1228222680000" + build_date="1228222680000" + version="[null]" + path="[null]"/> - + parent_snapshot_id="[null]" + root_component_uuid="uuid_2" + root_snapshot_id="[null]" + status="P" + islast="[false]" + purge_status="1" + period1_mode="[null]" + period1_param="[null]" + period1_date="[null]" + period2_mode="[null]" + period2_param="[null]" + period2_date="[null]" + period3_mode="[null]" + period3_param="[null]" + period3_date="[null]" + period4_mode="[null]" + period4_param="[null]" + period4_date="[null]" + period5_mode="[null]" + period5_param="[null]" + period5_date="[null]" + depth="[null]" + scope="PRJ" + qualifier="TRK" + created_at="1228222680000" + build_date="1228222680000" + version="[null]" + path="[null]"/> + text_value="[null]" + alert_status="[null]" + description="[null]" + measure_data="[null]"/> -