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:
+14
-8
@@ -646,14 +646,20 @@ says is complete carries no continuation point. `NumValuesPerNode == 0` stays un
|
||||
forward) whatever the resume request's details say. Reverse tie clusters are over-fetched forward and sliced from the newest
|
||||
end, exactly as Raw does. Before #675 a capped reverse read silently returned the EARLIEST N, ascending, Good, with no
|
||||
continuation point.
|
||||
- **End-only events reads read backward (#675).** `StartTime` unset (`DateTime.MinValue`) with an `EndTime` and
|
||||
`NumValuesPerNode > 0` pages backward from `EndTime` over `(MinValue, EndTime]` — the same rule as Raw. Start-only reads still
|
||||
page forward to an open upper bound (`DateTime.MaxValue`, UTC). Without a cap an end-only read keeps its forward
|
||||
`[MinValue, EndTime)` meaning.
|
||||
- **Backends.** The server calls `IHistorianDataSource.ReadEventsAsync(…, bool reverse, …)`. SqlServer and QuestDB serve reverse
|
||||
natively (`… > lo AND … <= hi ORDER BY time DESC, id DESC`). **Gateway and Null refuse a reverse read** (the interface default
|
||||
throws `NotSupportedException`) and the node answers `BadHistoryOperationUnsupported` — the gateway streams ascending only and
|
||||
rejects `end < start`, and draining the whole window to keep its tail would be exactly the unbounded read the cap prevents.
|
||||
- **End-only events reads: backward on SqlServer/QuestDB, forward on Gateway (#675).** `StartTime` unset
|
||||
(`DateTime.MinValue`) with an `EndTime` and `NumValuesPerNode > 0` pages backward from `EndTime` over `(MinValue, EndTime]` —
|
||||
the same rule as Raw — **only on a backend that reports `SupportsReverseEvents`** (SqlServer, QuestDB). On Gateway (the
|
||||
default backend) and Null it keeps its pre-#675 forward `[MinValue, EndTime)` meaning, paged oldest-first, rather than
|
||||
becoming a refused reverse read. Start-only reads page forward to an open upper bound (`DateTime.MaxValue`, UTC) everywhere.
|
||||
Without a cap an end-only read is forward `[MinValue, EndTime)` everywhere.
|
||||
- **Backends.** The server checks `IHistorianDataSource.SupportsReverseEvents` and then calls
|
||||
`ReadEventsAsync(…, bool reverse, …)`. SqlServer and QuestDB serve reverse natively (`… > lo AND … <= hi ORDER BY time DESC,
|
||||
id DESC`) and report `true`. **Gateway and Null report `false`**: an explicit reverse read (`StartTime > EndTime`) is answered
|
||||
`BadHistoryOperationUnsupported` **without calling the backend** — the gateway streams ascending only and rejects `end < start`,
|
||||
and draining the whole window to keep its tail would be exactly the unbounded read the cap prevents. Because the refusal is a
|
||||
capability check, not a caught exception, a `NotSupportedException` thrown from inside a backend (a driver library) is logged
|
||||
as a backend fault like any other. Every implementation (and any future wrapper) must declare both members itself — the
|
||||
interface defaults (`false` / throw) would silently disable reverse reads; `HistorianDataSourceReverseEventsGuardTests` pins it.
|
||||
- **Gateway open upper bound.** The `DateTime.MaxValue` (UTC) bound of a start-only forward read is pinned offline through the
|
||||
published client's proto `Timestamp` conversion (captured by an in-process gRPC server) and the FILETIME encoding
|
||||
(`GatewayEventBoundsTests`). What a live AVEVA Historian does with a year-9999 bound is still unverified (needs the VPN).
|
||||
|
||||
Reference in New Issue
Block a user