fix(historian): #675 review follow-ups — SupportsReverseEvents capability, no catch-as-unsupported
- IHistorianDataSource.SupportsReverseEvents (default false; SqlServer/QuestDB true; Gateway/Null declare false). The node manager checks it BEFORE calling the source: an end-only capped events read becomes reverse only on a supporting backend (Gateway, the default, keeps the pre-#675 forward [MinValue, EndTime) paging), and an explicit reverse read on a non-supporting backend is BadHistoryOperationUnsupported without a call. - Removed the broad catch (NotSupportedException) in the events path: a NotSupportedException from inside a backend is now logged as a backend fault (Error), not an 'unsupported' Warning. - Null declares the reverse overload itself; guard test: every production IHistorianDataSource declares both the reverse overload and the capability (positive-control scan of all four backends). - Conformance: capability reported + oversized reverse tie cluster (forward over-fetch reversed == reverse read order; capped reverse read stopping inside the cluster flags truncation) on SqlServer + QuestDB. - docs/Historian.md: end-only capped events read is backward on SqlServer/QuestDB, forward on Gateway.
This commit is contained in:
@@ -101,6 +101,16 @@ public interface IHistorianDataSource : IDisposable
|
||||
int maxEvents,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Whether this backend can serve a reverse (newest-first) events read — the reverse overload of
|
||||
/// <see cref="ReadEventsAsync(string?, DateTime, DateTime, int, bool, CancellationToken)"/> (Gitea #675). The server
|
||||
/// checks it BEFORE calling the source: an explicit reverse read (<c>StartTime > EndTime</c>) on a backend that
|
||||
/// cannot is answered <c>BadHistoryOperationUnsupported</c> without a call, and an end-only capped read, which Part 11
|
||||
/// reads backward, keeps its forward <c>[MinValue, EndTime)</c> meaning there. Default <c>false</c>; an
|
||||
/// implementation (or a wrapper) declares it itself — a guard test pins that.
|
||||
/// </summary>
|
||||
bool SupportsReverseEvents => false;
|
||||
|
||||
/// <summary>
|
||||
/// Read historical alarm / event records in a chosen direction — the overload the server's HistoryReadEvents
|
||||
/// paging calls (Gitea #675).
|
||||
@@ -118,8 +128,9 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// <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) — which the server reports as <c>BadHistoryOperationUnsupported</c> rather than returning the
|
||||
/// earliest events in ascending order as if they were the answer.
|
||||
/// Null). The server never relies on that throw: it consults <see cref="SupportsReverseEvents"/> first and
|
||||
/// answers <c>BadHistoryOperationUnsupported</c> without calling, rather than returning the earliest events in
|
||||
/// ascending order as if they were the answer. A backend that serves reverse must override BOTH members.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="sourceName">The source name to filter events, or null to return events from all sources.</param>
|
||||
|
||||
@@ -55,6 +55,25 @@ public sealed class NullHistorianDataSource : IHistorianDataSource
|
||||
int maxEvents,
|
||||
CancellationToken cancellationToken) => Task.FromResult(EmptyEvents);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks><c>false</c> (Gitea #675): there is no history to read in either direction, and an explicit reverse read
|
||||
/// is answered <c>BadHistoryOperationUnsupported</c> like any backend that cannot read newest-first.</remarks>
|
||||
public bool SupportsReverseEvents => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Forward ⇒ the empty result; reverse ⇒ <see cref="NotSupportedException"/>, the interface contract for a
|
||||
/// backend without <see cref="SupportsReverseEvents"/>.</remarks>
|
||||
public Task<HistoricalEventsResult> ReadEventsAsync(
|
||||
string? sourceName,
|
||||
DateTime startUtc,
|
||||
DateTime endUtc,
|
||||
int maxEvents,
|
||||
bool reverse,
|
||||
CancellationToken cancellationToken) =>
|
||||
reverse
|
||||
? throw new NotSupportedException("The null historian cannot read events newest-first (reverse).")
|
||||
: Task.FromResult(EmptyEvents);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HistorianHealthSnapshot GetHealthSnapshot() => new(
|
||||
TotalQueries: 0,
|
||||
|
||||
Reference in New Issue
Block a user