diff --git a/src/SIL.Harmony.Tests/ModelSnapshotTests.cs b/src/SIL.Harmony.Tests/ModelSnapshotTests.cs index a638445..c829320 100644 --- a/src/SIL.Harmony.Tests/ModelSnapshotTests.cs +++ b/src/SIL.Harmony.Tests/ModelSnapshotTests.cs @@ -54,6 +54,28 @@ public async Task CanGetWordForASpecificCommit() thirdWord.Text.Should().Be("third"); } + [Fact] + public async Task CanGetWordBeforeASpecificCommit() + { + var entityId = Guid.NewGuid(); + var firstCommit = await WriteNextChange(SetWord(entityId, "first")); + var secondCommit = await WriteNextChange(SetWord(entityId, "second")); + var thirdCommit = await WriteNextChange(SetWord(entityId, "third")); + await ClearNonRootSnapshots(); + + //there's no state before the first commit created the entity + var beforeFirst = await DataModel.GetBeforeCommit(firstCommit.Id, entityId); + beforeFirst.Should().BeNull(); + + var beforeSecond = await DataModel.GetBeforeCommit(secondCommit.Id, entityId); + beforeSecond.Should().NotBeNull(); + beforeSecond!.Text.Should().Be("first"); + + var beforeThird = await DataModel.GetBeforeCommit(thirdCommit.Id, entityId); + beforeThird.Should().NotBeNull(); + beforeThird!.Text.Should().Be("second"); + } + [Fact] public async Task CanGetWordForASpecificTime() { diff --git a/src/SIL.Harmony/DataModel.cs b/src/SIL.Harmony/DataModel.cs index 8707fcb..69455bb 100644 --- a/src/SIL.Harmony/DataModel.cs +++ b/src/SIL.Harmony/DataModel.cs @@ -321,16 +321,52 @@ public async Task GetAtTime(DateTimeOffset time, Guid entityId) public async Task GetAtCommit(Guid commitId, Guid entityId) { await using var repo = await _crdtRepositoryFactory.CreateRepository(); - return await GetAtCommit(await repo.CurrentCommits().SingleAsync(c => c.Id == commitId), - entityId); + var commit = await repo.CurrentCommits().SingleAsync(c => c.Id == commitId); + return await GetAtCommit(commit, entityId, repo); } public async Task GetAtCommit(Commit commit, Guid entityId) { await using var repo = await _crdtRepositoryFactory.CreateRepository(); + return await GetAtCommit(commit, entityId, repo); + } + + private async Task GetAtCommit(Commit commit, Guid entityId, CrdtRepository repo) + { + var snapshot = await GetSnapshotAtCommit(commit, entityId, repo); + ArgumentNullException.ThrowIfNull(snapshot); + return (T)snapshot.Entity.DbObject; + } + + public async Task GetBeforeCommit(Guid commitId, Guid entityId) + { + await using var repo = await _crdtRepositoryFactory.CreateRepository(); + var commit = await repo.CurrentCommits().SingleAsync(c => c.Id == commitId); + return await GetBeforeCommit(commit, entityId, repo); + } + + public async Task GetBeforeCommit(Commit commit, Guid entityId) + { + await using var repo = await _crdtRepositoryFactory.CreateRepository(); + return await GetBeforeCommit(commit, entityId, repo); + } + + private async Task GetBeforeCommit(Commit commit, Guid entityId, CrdtRepository repo) + { + var previousCommit = await repo.FindPreviousCommit(commit); + //there's no state before the first commit + if (previousCommit is null) return default; + var snapshot = await GetSnapshotAtCommit(previousCommit, entityId, repo); + //the entity did not exist before the given commit + if (snapshot is null) return default; + return (T)snapshot.Entity.DbObject; + } + + private async Task GetSnapshotAtCommit(Commit commit, Guid entityId, CrdtRepository repo) + { var repository = repo.GetScopedRepository(commit); var snapshot = await repository.GetCurrentSnapshotByObjectId(entityId, false); - ArgumentNullException.ThrowIfNull(snapshot); + if (snapshot is null) return null; var newCommits = await repository.CurrentCommits() .Include(c => c.ChangeEntities) .WhereAfter(snapshot.Commit) @@ -347,7 +383,7 @@ public async Task GetAtCommit(Commit commit, Guid entityId) snapshot = snapshots[snapshot.EntityId]; } - return (T)snapshot.Entity.DbObject; + return snapshot; } public async Task GetSyncState()