fix(history): advertise HistoryServerCapabilities from the wired backend (#881)
v2-ci / unit-tests (push) Has been cancelled
v2-ci / unit-tests (push) Has been cancelled
Every Boolean under Server/ServerCapabilities/HistoryServerCapabilities read false (the SDK default) even though data and event history were served, so a spec-following client skipped HistoryRead entirely. Each backend now declares what it serves through its own interface: - IHistorianDataSource.ReadCapabilities (Data / Events / ServerTimestamps): Gateway Data|Events; SqlServer + QuestDb add ServerTimestamps (they store ServerTimeUtc, the Gateway stamps read time); Null declares None. A reflection guard requires every production source to declare it itself. - IHistorianDataUpdater.Capabilities (required member): SqlServer and QuestDb declare Insert/Replace/Update/DeleteRaw/DeleteAtTime; the Gateway provider has no updater. The node manager writes the flags on every source/updater assignment (AdvertisedHistoryCapabilities), and the same declarations drive dispatch: an update the updater does not declare is BadHistoryOperationUnsupported without reaching it, the HistoryWrite AccessLevel bit follows a non-empty declaration, and event-history notifier promotion follows the Events flag. Event and annotation update flags stay false (nothing dispatches them). Tests: booted-server wire test per backend + Null + a SqlServer->Gateway rewire pairing on one session; dispatch gating unit test; the #801 live gate now asserts the Gateway flags (not run here). Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -721,6 +721,11 @@ public sealed class GatewayHistorianDataSource : IHistorianDataSource, IAsyncDis
|
||||
public IReadOnlySet<HistoryAggregateType> SupportedAggregates =>
|
||||
_enableNativeTimeWeighted ? ServedAggregates : RawEvaluatedAggregates;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Data + events, but NOT ServerTimestamps: AVEVA keeps no per-sample server time, so SampleMapper stamps the read
|
||||
// time — advertising ServerTimestampSupported would promise a stored value we do not have (lmxopcua#881).
|
||||
public HistorianReadCapabilities ReadCapabilities => HistorianReadCapabilities.Data | HistorianReadCapabilities.Events;
|
||||
|
||||
/// <inheritdoc />
|
||||
// A reverse (newest-first) read is refused with NotSupportedException — the gateway's
|
||||
// ReadEvents streams ascending only and rejects end < start. The server never calls this with
|
||||
|
||||
@@ -438,6 +438,11 @@ public sealed class QuestDbHistorianDataSource : IHistorianDataSource
|
||||
/// <inheritdoc />
|
||||
public IReadOnlySet<HistoryAggregateType> SupportedAggregates => AggregateFamilies.Served;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Data + events; every sample row stores its server time and a raw read returns it (lmxopcua#881).
|
||||
public HistorianReadCapabilities ReadCapabilities =>
|
||||
HistorianReadCapabilities.Data | HistorianReadCapabilities.Events | HistorianReadCapabilities.ServerTimestamps;
|
||||
|
||||
|
||||
// [start, end) by event time (a reversed window is swapped), ordered by (ts, event_id) ascending.
|
||||
// maxEvents <= 0 or int.MaxValue => unbounded. Otherwise maxEvents + 1 rows are fetched; when the
|
||||
|
||||
@@ -87,6 +87,10 @@ internal sealed class QuestDbHistorianDataUpdater : IHistorianDataUpdater
|
||||
public void Dispose() => sem.Release();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
// All five data updates; the deletes are soft (tombstone rows), which a reader cannot tell from a hard delete.
|
||||
public HistoryUpdateCapabilities Capabilities => HistoryUpdateCapabilities.AllData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IReadOnlyList<uint>> UpdateDataAsync(string tag, HistoryUpdateMode mode,
|
||||
IReadOnlyList<HistorizationValue> values, HistoryUpdateContext context, CancellationToken ct)
|
||||
|
||||
+6
@@ -363,6 +363,12 @@ public sealed class SqlServerHistorianDataSource : IHistorianDataSource
|
||||
/// <inheritdoc />
|
||||
public IReadOnlySet<HistoryAggregateType> SupportedAggregates => AggregateFamilies.Served;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Data + events; every sample row stores ServerTimeUtc (written by the recorder / HistoryUpdate) and a raw read
|
||||
// returns it (lmxopcua#881).
|
||||
public HistorianReadCapabilities ReadCapabilities =>
|
||||
HistorianReadCapabilities.Data | HistorianReadCapabilities.Events | HistorianReadCapabilities.ServerTimestamps;
|
||||
|
||||
|
||||
// <remarks>
|
||||
// <c>[start, end)</c> by <c>EventTimeUtc</c> (a reversed window is swapped), ordered by
|
||||
|
||||
+5
@@ -67,6 +67,11 @@ internal sealed class SqlServerHistorianDataUpdater : IHistorianDataUpdater
|
||||
"HistoryUpdate needs the Part 11 modification trail (apply 002_sample_modified.sql and restart).");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
// All five data updates (MERGE / OUTPUT-trail deletes). On a trail-less v1 schema (AutoCreateSchema=false, logged at
|
||||
// boot) every call still throws NotSupportedException at run time → BadHistoryOperationUnsupported.
|
||||
public HistoryUpdateCapabilities Capabilities => HistoryUpdateCapabilities.AllData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IReadOnlyList<uint>> UpdateDataAsync(string tag, HistoryUpdateMode mode,
|
||||
IReadOnlyList<HistorizationValue> values, HistoryUpdateContext context, CancellationToken ct)
|
||||
|
||||
Reference in New Issue
Block a user