feat(historian): HistoryRead override (Raw/Processed/AtTime) over IHistorianDataSource

This commit is contained in:
Joseph Doherty
2026-06-14 19:31:39 -04:00
parent 50e1141fc2
commit 13fba8f8fb
4 changed files with 910 additions and 0 deletions
@@ -0,0 +1,81 @@
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
/// <summary>
/// A no-op <see cref="IHistorianDataSource"/> — the server's default historian backend when no
/// real historian has been registered. Every read returns an EMPTY result (no samples / events,
/// null continuation point) so the node-manager's HistoryRead override surfaces
/// <c>GoodNoData</c> for a historized node rather than faulting, and
/// <see cref="GetHealthSnapshot"/> reports a fully-disabled (zeroed, disconnected) snapshot.
/// <para>
/// A process-wide singleton via <see cref="Instance"/> (private ctor): it carries no state
/// and is immutable, so one shared instance is safe to assign as the node-manager's
/// <c>HistorianDataSource</c> default until the Host wires a real source post-start (Task 5).
/// </para>
/// </summary>
public sealed class NullHistorianDataSource : IHistorianDataSource
{
/// <summary>The shared singleton instance.</summary>
public static readonly NullHistorianDataSource Instance = new();
private static readonly HistoryReadResult EmptyRead = new(Array.Empty<DataValueSnapshot>(), null);
private static readonly HistoricalEventsResult EmptyEvents = new(Array.Empty<HistoricalEvent>(), null);
private NullHistorianDataSource()
{
}
/// <inheritdoc />
public Task<HistoryReadResult> ReadRawAsync(
string fullReference,
DateTime startUtc,
DateTime endUtc,
uint maxValuesPerNode,
CancellationToken cancellationToken) => Task.FromResult(EmptyRead);
/// <inheritdoc />
public Task<HistoryReadResult> ReadProcessedAsync(
string fullReference,
DateTime startUtc,
DateTime endUtc,
TimeSpan interval,
HistoryAggregateType aggregate,
CancellationToken cancellationToken) => Task.FromResult(EmptyRead);
/// <inheritdoc />
public Task<HistoryReadResult> ReadAtTimeAsync(
string fullReference,
IReadOnlyList<DateTime> timestampsUtc,
CancellationToken cancellationToken) => Task.FromResult(EmptyRead);
/// <inheritdoc />
public Task<HistoricalEventsResult> ReadEventsAsync(
string? sourceName,
DateTime startUtc,
DateTime endUtc,
int maxEvents,
CancellationToken cancellationToken) => Task.FromResult(EmptyEvents);
/// <summary>
/// Returns a fully-disabled snapshot — no connections open, every counter zero, every nullable
/// field null, no cluster nodes. Pure; never blocks.
/// </summary>
public HistorianHealthSnapshot GetHealthSnapshot() => new(
TotalQueries: 0,
TotalSuccesses: 0,
TotalFailures: 0,
ConsecutiveFailures: 0,
LastSuccessTime: null,
LastFailureTime: null,
LastError: null,
ProcessConnectionOpen: false,
EventConnectionOpen: false,
ActiveProcessNode: null,
ActiveEventNode: null,
Nodes: Array.Empty<HistorianClusterNodeState>());
/// <summary>No-op — the null source owns no unmanaged resources.</summary>
public void Dispose()
{
// Stateless singleton; nothing to release.
}
}