fix(historian): #691.4 review — Null reports SupportsReverseEvents = true, like raw
The null source has no history in either direction; reporting false made the server walk an empty reverse events read back through three empty forward reads. It now answers the reverse overload with the empty result itself (guard test unchanged: it still declares both members).
This commit is contained in:
@@ -143,8 +143,8 @@ public interface IHistorianDataSource : IDisposable
|
|||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// The default forwards a forward read to the five-argument overload and throws
|
/// The default forwards a forward read to the five-argument overload and throws
|
||||||
/// <see cref="NotSupportedException"/> for a reverse one — a backend that cannot read newest-first (Gateway,
|
/// <see cref="NotSupportedException"/> for a reverse one — a backend that cannot read newest-first (the
|
||||||
/// Null). The server never relies on that throw: it consults <see cref="SupportsReverseEvents"/> first and walks
|
/// Gateway). The server never relies on that throw: it consults <see cref="SupportsReverseEvents"/> first and walks
|
||||||
/// such a read back from forward reads (#691.4), never returning the earliest events in ascending order as if
|
/// such a read back from forward reads (#691.4), never returning the earliest events in ascending order as if
|
||||||
/// they were the answer. A backend that serves reverse natively must override BOTH members.
|
/// they were the answer. A backend that serves reverse natively must override BOTH members.
|
||||||
/// </para>
|
/// </para>
|
||||||
|
|||||||
@@ -61,13 +61,12 @@ public sealed class NullHistorianDataSource : IHistorianDataSource
|
|||||||
public bool SupportsReverseRaw => true;
|
public bool SupportsReverseRaw => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <remarks><c>false</c> (Gitea #675): there is no history to read in either direction; the server walks a reverse
|
/// <remarks><c>true</c>, like <see cref="SupportsReverseRaw"/> (Gitea #691.4 review; <c>false</c> under #675): there is no
|
||||||
/// read back from (empty) forward reads like any backend that cannot read newest-first (#691.4).</remarks>
|
/// history to read in either direction — no need for the server to walk back through empty forward reads.</remarks>
|
||||||
public bool SupportsReverseEvents => false;
|
public bool SupportsReverseEvents => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <remarks>Forward ⇒ the empty result; reverse ⇒ <see cref="NotSupportedException"/>, the interface contract for a
|
/// <remarks>The empty result in either direction.</remarks>
|
||||||
/// backend without <see cref="SupportsReverseEvents"/>.</remarks>
|
|
||||||
public Task<HistoricalEventsResult> ReadEventsAsync(
|
public Task<HistoricalEventsResult> ReadEventsAsync(
|
||||||
string? sourceName,
|
string? sourceName,
|
||||||
DateTime startUtc,
|
DateTime startUtc,
|
||||||
@@ -75,9 +74,7 @@ public sealed class NullHistorianDataSource : IHistorianDataSource
|
|||||||
int maxEvents,
|
int maxEvents,
|
||||||
bool reverse,
|
bool reverse,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
reverse
|
Task.FromResult(EmptyEvents);
|
||||||
? throw new NotSupportedException("The null historian cannot read events newest-first (reverse).")
|
|
||||||
: Task.FromResult(EmptyEvents);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|||||||
@@ -5291,7 +5291,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
|||||||
reverse = startUtc > endUtc;
|
reverse = startUtc > endUtc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A reverse read on a backend that cannot read newest-first (Gateway, Null) is never sent there (#675 refused it
|
// A reverse read on a backend that cannot read newest-first (the Gateway) is never sent there (#675 refused it
|
||||||
// here); since #691.4 it is walked back from bounded forward reads — the raw walk (#686), with ties ordered by
|
// here); since #691.4 it is walked back from bounded forward reads — the raw walk (#686), with ties ordered by
|
||||||
// EventId because the gateway orders by event time only. Decided by the capability, not by catching an
|
// EventId because the gateway orders by event time only. Decided by the capability, not by catching an
|
||||||
// exception, so a NotSupportedException thrown from inside a backend stays a backend fault.
|
// exception, so a NotSupportedException thrown from inside a backend stays a backend fault.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ internal readonly record struct ReverseEventsPage(List<HistoricalEvent> Events,
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gitea #691.4: serves a Part 11 reverse events read — <c>(lo, hi]</c> newest-first — on a backend that reads events
|
/// Gitea #691.4: serves a Part 11 reverse events read — <c>(lo, hi]</c> newest-first — on a backend that reads events
|
||||||
/// forward only (<see cref="IHistorianDataSource.SupportsReverseEvents"/> = <c>false</c>: Gateway, Null) with the
|
/// forward only (<see cref="IHistorianDataSource.SupportsReverseEvents"/> = <c>false</c>: the Gateway) with the
|
||||||
/// <see cref="ReverseWalk"/>. A segment read is cut short when the source flags truncation (its continuation point); ties
|
/// <see cref="ReverseWalk"/>. A segment read is cut short when the source flags truncation (its continuation point); ties
|
||||||
/// are ordered by <see cref="EventIdTieOrder"/> because the gateway orders by event time only.
|
/// are ordered by <see cref="EventIdTieOrder"/> because the gateway orders by event time only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
+7
-5
@@ -68,17 +68,19 @@ public sealed class NullHistorianDataSourceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// #675: the direction-aware overload's default — forward is the five-argument read, reverse is refused
|
/// #691.4 review: like raw, the null source reads events in either direction (empty) and says so — the server sends
|
||||||
/// (the server answers <c>BadHistoryOperationUnsupported</c>).
|
/// it the reverse read directly instead of walking back through empty forward reads (it refused reverse under #675).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Direction_aware_ReadEventsAsync_forwards_forward_and_refuses_reverse()
|
public async Task Direction_aware_ReadEventsAsync_is_empty_in_both_directions()
|
||||||
{
|
{
|
||||||
IHistorianDataSource source = Source;
|
IHistorianDataSource source = Source;
|
||||||
|
source.SupportsReverseEvents.ShouldBeTrue();
|
||||||
(await source.ReadEventsAsync(null, DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, 0, reverse: false, Ct))
|
(await source.ReadEventsAsync(null, DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, 0, reverse: false, Ct))
|
||||||
.Events.ShouldBeEmpty();
|
.Events.ShouldBeEmpty();
|
||||||
await Should.ThrowAsync<NotSupportedException>(() =>
|
var reverse = await source.ReadEventsAsync(null, DateTime.UtcNow, DateTime.UtcNow.AddHours(-1), 0, reverse: true, Ct);
|
||||||
source.ReadEventsAsync(null, DateTime.UtcNow, DateTime.UtcNow.AddHours(-1), 0, reverse: true, Ct));
|
reverse.Events.ShouldBeEmpty();
|
||||||
|
reverse.ContinuationPoint.ShouldBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>GetHealthSnapshot reports a fully-disabled (zeroed, disconnected, no-node) shape.</summary>
|
/// <summary>GetHealthSnapshot reports a fully-disabled (zeroed, disconnected, no-node) shape.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user