Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

History apis #18

Merged
merged 10 commits into from
Nov 7, 2024
19 changes: 15 additions & 4 deletions src/SIL.Harmony.Core/QueryHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,21 @@ public static IQueryable<T> WhereAfter<T>(this IQueryable<T> queryable, T after)
|| (after.HybridDateTime.DateTime == c.HybridDateTime.DateTime && after.HybridDateTime.Counter == c.HybridDateTime.Counter && after.Id < c.Id));
}

public static IQueryable<T> WhereBefore<T>(this IQueryable<T> queryable, T after) where T : CommitBase
public static IQueryable<T> WhereBefore<T>(this IQueryable<T> queryable, T before, bool inclusive = false) where T : CommitBase
{
return queryable.Where(c => c.HybridDateTime.DateTime < after.HybridDateTime.DateTime
|| (c.HybridDateTime.DateTime == after.HybridDateTime.DateTime && c.HybridDateTime.Counter < after.HybridDateTime.Counter)
|| (c.HybridDateTime.DateTime == after.HybridDateTime.DateTime && c.HybridDateTime.Counter == after.HybridDateTime.Counter && c.Id < after.Id));
if (inclusive)
{

return queryable.Where(c => c.HybridDateTime.DateTime < before.HybridDateTime.DateTime
|| (c.HybridDateTime.DateTime == before.HybridDateTime.DateTime &&
c.HybridDateTime.Counter < before.HybridDateTime.Counter)
|| (c.HybridDateTime.DateTime == before.HybridDateTime.DateTime &&
c.HybridDateTime.Counter == before.HybridDateTime.Counter &&
c.Id < before.Id)
|| c.Id == before.Id);
}
return queryable.Where(c => c.HybridDateTime.DateTime < before.HybridDateTime.DateTime
|| (c.HybridDateTime.DateTime == before.HybridDateTime.DateTime && c.HybridDateTime.Counter < before.HybridDateTime.Counter)
|| (c.HybridDateTime.DateTime == before.HybridDateTime.DateTime && c.HybridDateTime.Counter == before.HybridDateTime.Counter && c.Id < before.Id));
}
}
4 changes: 2 additions & 2 deletions src/SIL.Harmony.Tests/DataModelPerformanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ internal static async Task BulkInsertChanges(DataModelTestBase dataModelTest, in
};
commit.SetParentHash(parentHash);
parentHash = commit.Hash;
dataModelTest.DbContext.Commits.Add(commit);
dataModelTest.DbContext.Snapshots.Add(new ObjectSnapshot(await change.NewEntity(commit, null!), commit, true));
dataModelTest.DbContext.Add(commit);
dataModelTest.DbContext.Add(new ObjectSnapshot(await change.NewEntity(commit, null!), commit, true));
}

await dataModelTest.DbContext.SaveChangesAsync();
Expand Down
6 changes: 3 additions & 3 deletions src/SIL.Harmony.Tests/DataModelReferenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ public async Task SnapshotsDontGetMutatedByADelete()
{
var refAdd = await WriteNextChange(new AddAntonymReferenceChange(_word1Id, _word2Id));
await WriteNextChange(new DeleteChange<Word>(_word2Id));
var entitySnapshot1 = await DataModel.GetEntitySnapshotAtTime(refAdd.DateTime, _word1Id);
entitySnapshot1.Should().NotBeNull();
entitySnapshot1!.Entity.Is<Word>().AntonymId.Should().Be(_word2Id);
var word = await DataModel.GetAtCommit<Word>(refAdd.Id, _word1Id);
word.Should().NotBeNull();
word.AntonymId.Should().Be(_word2Id);
}

[Fact]
Expand Down
166 changes: 164 additions & 2 deletions src/SIL.Harmony.Tests/Db/QueryHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public class QueryHelperTests
private Commit Commit(Guid id, HybridDateTime hybridDateTime) =>
new(id) { ClientId = Guid.Empty, HybridDateTime = hybridDateTime };

private ObjectSnapshot Snapshot(Guid commitId, HybridDateTime hybridDateTime) => ObjectSnapshot.ForTesting(Commit(commitId, hybridDateTime));
private ObjectSnapshot Snapshot(Guid commitId, HybridDateTime hybridDateTime) =>
ObjectSnapshot.ForTesting(Commit(commitId, hybridDateTime));

private IQueryable<Commit> Commits(IEnumerable<Commit> commits) => commits.AsQueryable();
private IQueryable<ObjectSnapshot> Snapshots(IEnumerable<ObjectSnapshot> snapshots) => snapshots.AsQueryable();
Expand Down Expand Up @@ -283,6 +284,111 @@ public void WhereAfterSnapshot_FiltersOnId()
]);
}



[Fact]
public void WhereBeforeSnapshot_FiltersOnDate()
{
var filterCommit = Commit(Guid.NewGuid(), Time(2, 0));
var snapshots = Snapshots([
Snapshot(id1, Time(1, 0)),
Snapshot(id2, Time(3, 0)),
Snapshot(filterCommit.Id, filterCommit.HybridDateTime),
Snapshot(id3, Time(4, 0)),
hahn-kev marked this conversation as resolved.
Show resolved Hide resolved
]);
snapshots.WhereBefore(filterCommit).Select(c => c.CommitId).Should().BeEquivalentTo([
id1
]);
}

[Fact]
public void WhereBeforeSnapshotInclusive_FiltersOnDate()
{
var filterCommit = Commit(Guid.NewGuid(), Time(2, 0));
var snapshots = Snapshots([
Snapshot(id1, Time(1, 0)),
Snapshot(id2, Time(3, 0)),
Snapshot(id3, Time(4, 0)),
Snapshot(filterCommit.Id, filterCommit.HybridDateTime)
]);
snapshots.WhereBefore(filterCommit, inclusive: true).Select(c => c.CommitId).Should().BeEquivalentTo([
id1,
filterCommit.Id
]);
}

[Fact]
public void WhereBeforeSnapshot_FiltersOnCounter()
{
var filterCommit = Commit(Guid.NewGuid(), Time(1, 2));
var snapshots = Snapshots([
Snapshot(id1, Time(1, 1)),
Snapshot(id2, Time(1, 3)),
Snapshot(filterCommit.Id, filterCommit.HybridDateTime),
Snapshot(id3, Time(1, 4)),
]);
snapshots.WhereBefore(filterCommit).Select(c => c.CommitId).Should().BeEquivalentTo([
id1,
]);
}

[Fact]
public void WhereBeforeSnapshotInclusive_FiltersOnCounter()
{
var filterCommit = Commit(Guid.NewGuid(), Time(1, 2));
var snapshots = Snapshots([
Snapshot(id1, Time(1, 1)),
Snapshot(id2, Time(1, 3)),
Snapshot(id3, Time(1, 4)),
Snapshot(filterCommit.Id, filterCommit.HybridDateTime),
]);
snapshots.WhereBefore(filterCommit, inclusive: true).Select(c => c.CommitId).Should().BeEquivalentTo([
id1,
filterCommit.Id
]);
}

[Fact]
public void WhereBeforeSnapshot_FiltersOnId()
{
var hybridDateTime = Time(1, 1);
Guid[] ids = OrderedIds(4);
var commitId1 = ids[0];
var filterCommit = Commit(ids[1], hybridDateTime);
var commitId2 = ids[2];
var commitId3 = ids[3];
var snapshots = Snapshots([
Snapshot(commitId1, hybridDateTime),
Snapshot(commitId2, hybridDateTime),
Snapshot(commitId3, hybridDateTime),
Snapshot(filterCommit.Id, filterCommit.HybridDateTime),
]);
snapshots.WhereBefore(filterCommit).Select(c => c.CommitId).Should().BeEquivalentTo([
commitId1
]);
}

[Fact]
public void WhereBeforeSnapshotInclusive_FiltersOnId()
{
var hybridDateTime = Time(1, 1);
Guid[] ids = OrderedIds(4);
var commitId1 = ids[0];
var filterCommit = Commit(ids[1], hybridDateTime);
var commitId2 = ids[2];
var commitId3 = ids[3];
var snapshots = Snapshots([
Snapshot(commitId1, hybridDateTime),
Snapshot(filterCommit.Id, filterCommit.HybridDateTime),
Snapshot(commitId2, hybridDateTime),
Snapshot(commitId3, hybridDateTime),
]);
snapshots.WhereBefore(filterCommit, inclusive: true).Select(c => c.CommitId).Should().BeEquivalentTo([
commitId1,
filterCommit.Id
]);
}

[Fact]
public void WhereBeforeCommit_FiltersOnDate()
{
Expand All @@ -291,12 +397,29 @@ public void WhereBeforeCommit_FiltersOnDate()
Commit(id1, Time(1, 0)),
Commit(id2, Time(3, 0)),
Commit(id3, Time(4, 0)),
filterCommit
]);
commits.WhereBefore(filterCommit).Select(c => c.Id).Should().BeEquivalentTo([
id1
]);
}

[Fact]
public void WhereBeforeCommitInclusive_FiltersOnDate()
{
var filterCommit = Commit(Guid.NewGuid(), Time(2, 0));
var commits = Commits([
Commit(id1, Time(1, 0)),
Commit(id2, Time(3, 0)),
Commit(id3, Time(4, 0)),
filterCommit
]);
commits.WhereBefore(filterCommit, inclusive: true).Select(c => c.Id).Should().BeEquivalentTo([
id1,
filterCommit.Id
]);
}

[Fact]
public void WhereBeforeCommit_FiltersOnCounter()
{
Expand All @@ -305,9 +428,26 @@ public void WhereBeforeCommit_FiltersOnCounter()
Commit(id1, Time(1, 1)),
Commit(id2, Time(1, 3)),
Commit(id3, Time(1, 4)),
filterCommit
]);
commits.WhereBefore(filterCommit).Select(c => c.Id).Should().BeEquivalentTo([
id1
id1,
]);
}

[Fact]
public void WhereBeforeCommitInclusive_FiltersOnCounter()
{
var filterCommit = Commit(Guid.NewGuid(), Time(1, 2));
var commits = Commits([
Commit(id1, Time(1, 1)),
Commit(id2, Time(1, 3)),
Commit(id3, Time(1, 4)),
filterCommit
]);
commits.WhereBefore(filterCommit, inclusive: true).Select(c => c.Id).Should().BeEquivalentTo([
id1,
filterCommit.Id
]);
}

Expand All @@ -324,9 +464,31 @@ public void WhereBeforeCommit_FiltersOnId()
Commit(commitId1, hybridDateTime),
Commit(commitId2, hybridDateTime),
Commit(commitId3, hybridDateTime),
filterCommit
]);
commits.WhereBefore(filterCommit).Select(c => c.Id).Should().BeEquivalentTo([
commitId1
]);
}

[Fact]
public void WhereBeforeCommitInclusive_FiltersOnId()
{
var hybridDateTime = Time(1, 1);
Guid[] ids = OrderedIds(4);
var commitId1 = ids[0];
var filterCommit = Commit(ids[1], hybridDateTime);
var commitId2 = ids[2];
var commitId3 = ids[3];
var commits = Commits([
Commit(commitId1, hybridDateTime),
filterCommit,
Commit(commitId2, hybridDateTime),
Commit(commitId3, hybridDateTime),
]);
commits.WhereBefore(filterCommit, inclusive: true).Select(c => c.Id).Should().BeEquivalentTo([
commitId1,
filterCommit.Id
]);
}
}
6 changes: 3 additions & 3 deletions src/SIL.Harmony.Tests/DbContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task CanRoundTripDatesFromEf(int offset)
ClientId = Guid.NewGuid(),
HybridDateTime = new HybridDateTime(expectedDateTime, 0)
};
DbContext.Commits.Add(commit);
DbContext.Add(commit);
await DbContext.SaveChangesAsync();
var actualCommit = await DbContext.Commits.AsNoTracking().SingleOrDefaultAsyncEF(c => c.Id == commitId);
actualCommit!.HybridDateTime.DateTime.Should().Be(expectedDateTime, "EF");
Expand All @@ -46,7 +46,7 @@ public async Task CanRoundTripDatesFromLinq2Db(int offset)
var commitId = Guid.NewGuid();
var expectedDateTime = new DateTimeOffset(2000, 1, 1, 1, 11, 11, TimeSpan.FromHours(offset));

await DbContext.Commits.ToLinqToDBTable().AsValueInsertable()
await DbContext.Set<Commit>().ToLinqToDBTable().AsValueInsertable()
.Value(c => c.Id, commitId)
.Value(c => c.ClientId, Guid.NewGuid())
.Value(c => c.HybridDateTime.DateTime, expectedDateTime)
Expand Down Expand Up @@ -74,7 +74,7 @@ public async Task CanFilterCommitsByDateTime(double scale)
for (int i = 0; i < 50; i++)
{
var offset = new TimeSpan((long)(i * scale));
DbContext.Commits.Add(new Commit
DbContext.Add(new Commit
{
ClientId = Guid.NewGuid(),
HybridDateTime = new HybridDateTime(baseDateTime.Add(offset), 0)
Expand Down
57 changes: 55 additions & 2 deletions src/SIL.Harmony.Tests/ModelSnapshotTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,59 @@ public async Task ModelSnapshotShowsMultipleChanges()
snapshot.LastChange.Should().Be(secondChange.DateTime);
}

[Fact]
public async Task CanGetWordForASpecificCommit()
myieye marked this conversation as resolved.
Show resolved Hide resolved
{
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.GetAtCommit<Word>(firstCommit.Id, entityId);
firstWord.Should().NotBeNull();
firstWord.Text.Should().Be("first");

var secondWord = await DataModel.GetAtCommit<Word>(secondCommit.Id, entityId);
secondWord.Should().NotBeNull();
secondWord.Text.Should().Be("second");

var thirdWord = await DataModel.GetAtCommit<Word>(thirdCommit.Id, entityId);
thirdWord.Should().NotBeNull();
thirdWord.Text.Should().Be("third");
}

[Fact]
public async Task CanGetWordForASpecificTime()
myieye marked this conversation as resolved.
Show resolved Hide resolved
{
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"));
//ensures that SnapshotWorker.ApplyCommitsToSnapshots will be called when getting the snapshots
await ClearNonRootSnapshots();
hahn-kev marked this conversation as resolved.
Show resolved Hide resolved
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();
}

[Theory]
[InlineData(10)]
[InlineData(100)]
Expand All @@ -56,7 +109,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 @@ -77,7 +130,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
Loading