fix(historian): page by the backend's timestamp resolution; QuestDB AtTime stamps the requested time
IHistorianDataSource.TimestampResolutionTicks (default 1; QuestDB 10). A drained tie cluster now steps off T by the resolution, so a forward one-value-per-page read on a microsecond backend no longer re-reads the same sample forever (C1). QuestDB AtTime results carry the client's original request time (M3); regression for the NEXT seek skipping a tombstone (M1). Conformance backend wires the real writer and updater. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -345,6 +345,51 @@ public sealed class HistoryPagingTests
|
||||
store.TryTake(null, cp3).ShouldNotBeNull(); // retained
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SliceTieCluster_forward_drain_steps_by_the_backend_resolution()
|
||||
{
|
||||
// A microsecond backend (QuestDB, 10 ticks) truncates T + 1 tick back to T, so the drained cluster must be
|
||||
// stepped off by the resolution, not by one tick, or the same sample comes back forever.
|
||||
var t = new DateTime(2026, 1, 1, 0, 0, 5, DateTimeKind.Utc);
|
||||
|
||||
HistoryPaging.SliceTieCluster(
|
||||
clusterCount: 1, skip: 1, cap: 1, boundaryT: t, endUtc: t.AddHours(1),
|
||||
out _, out var sliceCount, out var nextStartUtc, out var nextSkip, resolutionTicks: 10);
|
||||
|
||||
sliceCount.ShouldBe(0);
|
||||
nextStartUtc.ShouldBe(t.AddTicks(10));
|
||||
nextSkip.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SliceTieCluster_reverse_drain_steps_down_by_the_backend_resolution()
|
||||
{
|
||||
var t = new DateTime(2026, 1, 1, 0, 0, 5, DateTimeKind.Utc);
|
||||
|
||||
HistoryPaging.SliceTieCluster(
|
||||
clusterCount: 1, skip: 1, cap: 1, boundaryT: t, endUtc: t.AddHours(-1),
|
||||
out _, out _, out var nextStartUtc, out _, reverse: true, resolutionTicks: 10);
|
||||
|
||||
nextStartUtc.ShouldBe(t.AddTicks(-10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SliceTieCluster_forward_drain_terminates_when_one_resolution_step_passes_the_end()
|
||||
{
|
||||
var t = new DateTime(2026, 1, 1, 0, 0, 5, DateTimeKind.Utc);
|
||||
|
||||
HistoryPaging.SliceTieCluster(
|
||||
clusterCount: 1, skip: 1, cap: 1, boundaryT: t, endUtc: t.AddTicks(9),
|
||||
out _, out _, out var nextStartUtc, out _, resolutionTicks: 10);
|
||||
|
||||
nextStartUtc.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SliceTieCluster_rejects_a_resolution_below_one() =>
|
||||
Should.Throw<ArgumentOutOfRangeException>(() => HistoryPaging.SliceTieCluster(
|
||||
1, 1, 1, DateTime.UtcNow, DateTime.UtcNow.AddHours(1), out _, out _, out _, out _, resolutionTicks: 0));
|
||||
|
||||
private static HistoryContinuationState RawState() => new(
|
||||
"WW.Tag", DateTime.UtcNow, DateTime.UtcNow.AddHours(1),
|
||||
BoundarySkipCount: 1, NumValuesPerNode: 50);
|
||||
|
||||
Reference in New Issue
Block a user