Skip to content

Commit

Permalink
don't support creating a socped repo with a date time, push that to t…
Browse files Browse the repository at this point in the history
…he data model and add a method for getting an object at a given time
  • Loading branch information
hahn-kev committed Oct 31, 2024
1 parent 73d1286 commit 33b1aba
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 43 deletions.
30 changes: 28 additions & 2 deletions src/SIL.Harmony.Tests/ModelSnapshotTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,32 @@ public async Task CanGetWordForASpecificCommit()
thirdWord.Text.Should().Be("third");
}

[Fact]
public async Task CanGetWordForASpecificTime()
{
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();
var firstWord = await DataModel.GetAtTime<Word>(firstCommit.DateTime.AddMinutes(5), entityId);
firstWord.Should().NotBeNull();
firstWord.Text.Should().Be("first");

var secondWord = await DataModel.GetAtTime<Word>(secondCommit.DateTime.AddMinutes(5), entityId);
secondWord.Should().NotBeNull();
secondWord.Text.Should().Be("second");

//just before the 3rd commit should still be second
secondWord = await DataModel.GetAtTime<Word>(thirdCommit.DateTime.Subtract(TimeSpan.FromSeconds(5)), entityId);
secondWord.Should().NotBeNull();
secondWord.Text.Should().Be("second");

var thirdWord = await DataModel.GetAtTime<Word>(thirdCommit.DateTime.AddMinutes(5), entityId);
thirdWord.Should().NotBeNull();
thirdWord.Text.Should().Be("third");
}

private Task ClearNonRootSnapshots()
{
return DbContext.Snapshots.Where(s => !s.IsRoot).ExecuteDeleteAsync();
Expand Down Expand Up @@ -82,7 +108,7 @@ public async Task CanGetSnapshotFromEarlier(int changeCount)

for (int i = 0; i < changeCount; i++)
{
var snapshots = await DataModel.GetSnapshotsAt(changes[i].DateTime);
var snapshots = await DataModel.GetSnapshotsAtCommit(changes[i]);
var entry = snapshots[entityId].Entity.Is<Word>();
entry.Text.Should().Be($"change {i}");
snapshots.Values.Should().HaveCount(1 + i);
Expand All @@ -103,7 +129,7 @@ await AddCommitsViaSync(Enumerable.Range(0, changeCount)
//delete snapshots so when we get at then we need to re-apply
await DbContext.Snapshots.Where(s => !s.IsRoot).ExecuteDeleteAsync();

var computedModelSnapshots = await DataModel.GetSnapshotsAt(latestSnapshot.Commit.DateTime);
var computedModelSnapshots = await DataModel.GetSnapshotsAtCommit(latestSnapshot.Commit);

var entitySnapshot = computedModelSnapshots.Should().ContainSingle().Subject.Value;
entitySnapshot.Should().BeEquivalentTo(latestSnapshot, options => options.Excluding(snapshot => snapshot.Id).Excluding(snapshot => snapshot.Commit).Excluding(s => s.Entity.DbObject));
Expand Down
19 changes: 0 additions & 19 deletions src/SIL.Harmony.Tests/RepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,25 +192,6 @@ await _repository.AddSnapshots([
snapshots.Should().ContainSingle().Which.Commit.Id.Should().Be(ids[1]);
}

[Fact]
public async Task CurrentSnapshots_FiltersByDate()
{
var entityId = Guid.NewGuid();
var commit1Time = Time(1, 0);
var commit2Time = Time(3, 0);
await _repository.AddSnapshots([
Snapshot(entityId, Guid.NewGuid(), commit1Time),
Snapshot(entityId, Guid.NewGuid(), commit2Time),
]);

var snapshots = await _repository.CurrentSnapshots().Include(s => s.Commit).ToArrayAsync();
snapshots.Should().ContainSingle().Which.Commit.HybridDateTime.Should().BeEquivalentTo(commit2Time);

var newCurrentTime = Time(2, 0).DateTime;
snapshots = await _repository.GetScopedRepository(newCurrentTime).CurrentSnapshots().Include(s => s.Commit).ToArrayAsync();
snapshots.Should().ContainSingle().Which.Commit.HybridDateTime.Should().BeEquivalentTo(commit1Time);
}

[Fact]
public async Task ScopedRepo_CurrentSnapshots_FiltersByCounter()
{
Expand Down
46 changes: 33 additions & 13 deletions src/SIL.Harmony/DataModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ public class DataModel : ISyncable, IAsyncDisposable
private readonly IOptions<CrdtConfig> _crdtConfig;

//constructor must be internal because CrdtRepository is internal
internal DataModel(CrdtRepository crdtRepository, JsonSerializerOptions serializerOptions, IHybridDateTimeProvider timeProvider, IOptions<CrdtConfig> crdtConfig)
internal DataModel(CrdtRepository crdtRepository,
JsonSerializerOptions serializerOptions,
IHybridDateTimeProvider timeProvider,
IOptions<CrdtConfig> crdtConfig)
{
_crdtRepository = crdtRepository;
_serializerOptions = serializerOptions;
Expand Down Expand Up @@ -80,6 +83,7 @@ public async Task<Commit> AddChanges(
}

private List<Commit> _deferredCommits = [];

private async Task Add(Commit commit, bool deferSnapshotUpdates)
{
if (await _crdtRepository.HasCommit(commit.Id)) return;
Expand All @@ -97,6 +101,7 @@ private async Task Add(Commit commit, bool deferSnapshotUpdates)
{
_deferredCommits.Add(commit);
}

await transaction.CommitAsync();
}

Expand Down Expand Up @@ -191,7 +196,8 @@ private async Task ValidateCommits()

public async Task<ObjectSnapshot> GetLatestSnapshotByObjectId(Guid entityId)
{
return await _crdtRepository.GetCurrentSnapshotByObjectId(entityId) ?? throw new ArgumentException($"unable to find snapshot for entity {entityId}");
return await _crdtRepository.GetCurrentSnapshotByObjectId(entityId) ??
throw new ArgumentException($"unable to find snapshot for entity {entityId}");
}

public async Task<T?> GetLatest<T>(Guid objectId) where T : class
Expand All @@ -209,8 +215,10 @@ public IQueryable<T> QueryLatest<T>() where T : class
var q = _crdtRepository.GetCurrentObjects<T>();
if (q is IQueryable<IOrderableCrdt>)
{
q = q.OrderBy(o => EF.Property<double>(o, nameof(IOrderableCrdt.Order))).ThenBy(o => EF.Property<Guid>(o, nameof(IOrderableCrdt.Id)));
q = q.OrderBy(o => EF.Property<double>(o, nameof(IOrderableCrdt.Order)))
.ThenBy(o => EF.Property<Guid>(o, nameof(IOrderableCrdt.Id)));
}

return q;
}

Expand All @@ -219,29 +227,37 @@ public async Task<T> GetBySnapshotId<T>(Guid snapshotId)
return await _crdtRepository.GetObjectBySnapshotId<T>(snapshotId);
}

public async Task<Dictionary<Guid, ObjectSnapshot>> GetSnapshotsAt(DateTimeOffset dateTime)
public async Task<Dictionary<Guid, ObjectSnapshot>> GetSnapshotsAtCommit(Commit commit)
{
var repository = _crdtRepository.GetScopedRepository(dateTime);
var repository = _crdtRepository.GetScopedRepository(commit);
var (snapshots, pendingCommits) = await repository.GetCurrentSnapshotsAndPendingCommits();

if (pendingCommits.Length != 0)
{
snapshots = await SnapshotWorker.ApplyCommitsToSnapshots(snapshots, repository, pendingCommits, _crdtConfig.Value);
snapshots = await SnapshotWorker.ApplyCommitsToSnapshots(snapshots,
repository,
pendingCommits,
_crdtConfig.Value);
}

return snapshots;
}

public async Task<ObjectSnapshot?> GetEntitySnapshotAtTime(DateTimeOffset time, Guid entityId)
public async Task<T> GetAtTime<T>(DateTimeOffset time, Guid entityId)
{
var snapshots = await GetSnapshotsAt(time);
return snapshots.GetValueOrDefault(entityId);
var commitBefore = await _crdtRepository.CurrentCommits().LastOrDefaultAsync(c => c.HybridDateTime.DateTime <= time);
if (commitBefore is null) throw new ArgumentException("unable to find any commits");
return await GetAtCommit<T>(commitBefore, entityId);
}


public async Task<T> GetAtCommit<T>(Guid commitId, Guid entityId)
{
var commit = await _crdtRepository.CurrentCommits().SingleAsync(c => c.Id == commitId);
return await GetAtCommit<T>(await _crdtRepository.CurrentCommits().SingleAsync(c => c.Id == commitId),
entityId);
}

public async Task<T> GetAtCommit<T>(Commit commit, Guid entityId)
{
var repository = _crdtRepository.GetScopedRepository(commit);
var snapshot = await repository.GetCurrentSnapshotByObjectId(entityId, false);
ArgumentNullException.ThrowIfNull(snapshot);
Expand All @@ -251,15 +267,19 @@ public async Task<T> GetAtCommit<T>(Guid commitId, Guid entityId)
.ToArrayAsync();
if (newCommits.Length > 0)
{
var snapshots = await SnapshotWorker.ApplyCommitsToSnapshots(new Dictionary<Guid, ObjectSnapshot>([new KeyValuePair<Guid, ObjectSnapshot>(snapshot.EntityId, snapshot)]),
var snapshots = await SnapshotWorker.ApplyCommitsToSnapshots(
new Dictionary<Guid, ObjectSnapshot>([
new KeyValuePair<Guid, ObjectSnapshot>(snapshot.EntityId, snapshot)
]),
repository,
newCommits,
_crdtConfig.Value);
snapshot = snapshots[snapshot.EntityId];
}

return (T) snapshot.Entity.DbObject;
return (T)snapshot.Entity.DbObject;
}

public async Task<SyncState> GetSyncState()
{
return await _crdtRepository.GetCurrentSyncState();
Expand Down
9 changes: 0 additions & 9 deletions src/SIL.Harmony/Db/CrdtRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,6 @@ private async ValueTask SnapshotAdded(ObjectSnapshot objectSnapshot)
return entity is not null ? _dbContext.Entry(entity) : null;
}

public CrdtRepository GetScopedRepository(DateTimeOffset newCurrentTime)
{
return GetScopedRepository(new Commit(Guid.Empty)
{
ClientId = Guid.Empty,
HybridDateTime = new HybridDateTime(newCurrentTime, 0)
});
}

public CrdtRepository GetScopedRepository(Commit excludeChangesAfterCommit)
{
return new CrdtRepository(_dbContext, crdtConfig, excludeChangesAfterCommit);
Expand Down

0 comments on commit 33b1aba

Please sign in to comment.