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:
Joseph Doherty
2026-10-12 17:37:32 -04:00
parent d28e3e64f1
commit 574c3aa1b0
5 changed files with 16 additions and 17 deletions
@@ -143,8 +143,8 @@ public interface IHistorianDataSource : IDisposable
/// </para>
/// <para>
/// 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,
/// Null). The server never relies on that throw: it consults <see cref="SupportsReverseEvents"/> first and walks
/// <see cref="NotSupportedException"/> for a reverse one — a backend that cannot read newest-first (the
/// 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
/// they were the answer. A backend that serves reverse natively must override BOTH members.
/// </para>
@@ -61,13 +61,12 @@ public sealed class NullHistorianDataSource : IHistorianDataSource
public bool SupportsReverseRaw => true;
/// <inheritdoc />
/// <remarks><c>false</c> (Gitea #675): there is no history to read in either direction; the server walks a reverse
/// read back from (empty) forward reads like any backend that cannot read newest-first (#691.4).</remarks>
public bool SupportsReverseEvents => false;
/// <remarks><c>true</c>, like <see cref="SupportsReverseRaw"/> (Gitea #691.4 review; <c>false</c> under #675): there is no
/// history to read in either direction — no need for the server to walk back through empty forward reads.</remarks>
public bool SupportsReverseEvents => true;
/// <inheritdoc />
/// <remarks>Forward ⇒ the empty result; reverse ⇒ <see cref="NotSupportedException"/>, the interface contract for a
/// backend without <see cref="SupportsReverseEvents"/>.</remarks>
/// <remarks>The empty result in either direction.</remarks>
public Task<HistoricalEventsResult> ReadEventsAsync(
string? sourceName,
DateTime startUtc,
@@ -75,9 +74,7 @@ public sealed class NullHistorianDataSource : IHistorianDataSource
int maxEvents,
bool reverse,
CancellationToken cancellationToken) =>
reverse
? throw new NotSupportedException("The null historian cannot read events newest-first (reverse).")
: Task.FromResult(EmptyEvents);
Task.FromResult(EmptyEvents);
/// <inheritdoc />
/// <remarks>