fix(historian): #680 lane B — per-sample Count/Start/End/Delta per Part 13 + forward end-of-data Partial
Count: Good samples only, value-based status (all Bad => generic Bad), 0/Good on an empty interval inside the tag's data range [first, last] (both directions). Start/End: the raw first/last sample of any quality (own value, status, timestamp). Delta: Uncertain only when the interval's first or last sample is not usable. Forward end-of-data Partial (SDK m_endOfData, earlier edge inclusive) for every per-sample aggregate but Average (PercentGood/Bad left to lane A). Backends push down first/last-any-quality columns and fetch the tag's data range (earliest <= window end, latest >= window start) only when NeedsDataRange. SDK parity masks for Start/End/Delta/Count removed; new parity datasets.
This commit is contained in:
+35
-6
@@ -133,15 +133,44 @@ public sealed class BucketPlanTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_earliest_sample_is_needed_only_for_forward_reads_of_aggregates_other_than_Average()
|
||||
public void Data_ends_inside_from_the_interval_start_inclusive_to_its_end_exclusive()
|
||||
{
|
||||
// Review F2: backends skip the earliest-sample query when its answer cannot change a result.
|
||||
var p = BucketPlan.Create(At(0), At(40), TimeSpan.FromSeconds(20));
|
||||
p.DataEndsInside(0, At(5)).ShouldBeTrue();
|
||||
p.DataEndsInside(1, At(20)).ShouldBeTrue(); // on the earlier edge: the SDK's m_endOfData >= startTime
|
||||
p.DataEndsInside(0, At(20)).ShouldBeFalse();
|
||||
p.DataEndsInside(1, At(40)).ShouldBeFalse();
|
||||
p.DataEndsInside(0, null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_data_range_includes_both_of_its_edges()
|
||||
{
|
||||
var p = BucketPlan.Create(At(0), At(60), TimeSpan.FromSeconds(20));
|
||||
p.WithinDataRange(0, At(10), At(30)).ShouldBeTrue();
|
||||
p.WithinDataRange(1, At(10), At(30)).ShouldBeTrue();
|
||||
p.WithinDataRange(2, At(10), At(30)).ShouldBeFalse(); // [40,60) is after the last sample
|
||||
p.WithinDataRange(2, At(10), At(40)).ShouldBeTrue(); // last sample on the earlier edge
|
||||
p.WithinDataRange(0, At(20), At(30)).ShouldBeTrue(); // first sample on the later edge
|
||||
p.WithinDataRange(0, At(21), At(30)).ShouldBeFalse();
|
||||
p.WithinDataRange(0, null, At(30)).ShouldBeFalse();
|
||||
p.WithinDataRange(0, At(10), null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_data_range_is_needed_for_forward_reads_of_aggregates_other_than_Average_and_for_Count()
|
||||
{
|
||||
// Review F2 (#677) / #680: backends skip the earliest/latest-sample queries when their answer cannot change a
|
||||
// result — the start/end-of-data Partial rules are forward-only and not Average's; Count's in-range empty interval
|
||||
// (0, not BadNoData) applies in both directions.
|
||||
var fwd = BucketPlan.Create(At(0), At(40), TimeSpan.FromSeconds(10));
|
||||
var rev = BucketPlan.Create(At(40), At(0), TimeSpan.FromSeconds(10));
|
||||
AggregateEvaluator.NeedsEarliestSample(HistoryAggregateType.Count, fwd).ShouldBeTrue();
|
||||
AggregateEvaluator.NeedsEarliestSample(HistoryAggregateType.Average, fwd).ShouldBeFalse();
|
||||
AggregateEvaluator.NeedsEarliestSample(HistoryAggregateType.Count, rev).ShouldBeFalse();
|
||||
AggregateEvaluator.NeedsEarliestSample(HistoryAggregateType.Count, BucketPlan.Create(At(0), At(0), TimeSpan.Zero))
|
||||
AggregateEvaluator.NeedsDataRange(HistoryAggregateType.Count, fwd).ShouldBeTrue();
|
||||
AggregateEvaluator.NeedsDataRange(HistoryAggregateType.Minimum, fwd).ShouldBeTrue();
|
||||
AggregateEvaluator.NeedsDataRange(HistoryAggregateType.Average, fwd).ShouldBeFalse();
|
||||
AggregateEvaluator.NeedsDataRange(HistoryAggregateType.Count, rev).ShouldBeTrue();
|
||||
AggregateEvaluator.NeedsDataRange(HistoryAggregateType.Minimum, rev).ShouldBeFalse();
|
||||
AggregateEvaluator.NeedsDataRange(HistoryAggregateType.Count, BucketPlan.Create(At(0), At(0), TimeSpan.Zero))
|
||||
.ShouldBeFalse();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user