feat(historian-gateway): ReadEventsAsync alarm-history via gateway ReadEvents (+ truncation signal)
Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
+39
-4
@@ -133,23 +133,58 @@ public sealed class GatewayHistorianDataSource : IHistorianDataSource, IAsyncDis
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Depends on the target gateway running with <c>RuntimeDb:EventReadsEnabled=true</c> (the
|
||||
/// SQL alarm-history path). The <paramref name="sourceName"/> is passed through to the
|
||||
/// gateway, but its SQL <c>ReadEvents</c> source filter may not be present yet — so this
|
||||
/// adapter also filters the mapped events by <see cref="HistoricalEvent.SourceName"/>
|
||||
/// client-side (defensive; remove once the server filter is confirmed). The
|
||||
/// <paramref name="maxEvents"/> cap is enforced client-side by early stream termination:
|
||||
/// a non-positive value applies no client cap (the gateway may still apply its
|
||||
/// <c>EventReadMaxRows</c>); a positive cap stops at N and sets a non-null
|
||||
/// <see cref="HistoricalEventsResult.ContinuationPoint"/> iff at least one further matching
|
||||
/// event existed (the Core.Abstractions-009 truncation signal).
|
||||
/// </remarks>
|
||||
public async Task<HistoricalEventsResult> ReadEventsAsync(
|
||||
string? sourceName, DateTime startUtc, DateTime endUtc, int maxEvents,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var events = new List<HistorianEvent>();
|
||||
var hasCap = maxEvents > 0;
|
||||
var collected = new List<HistoricalEvent>(hasCap ? maxEvents : 0);
|
||||
var truncated = false;
|
||||
|
||||
await foreach (var wireEvent in _client
|
||||
.ReadEventsAsync(sourceName, startUtc, endUtc, maxEvents, cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
{
|
||||
events.Add(wireEvent);
|
||||
var mapped = EventMapper.ToHistoricalEvent(wireEvent);
|
||||
|
||||
// Defensive client-side source filter: the gateway's SQL ReadEvents source filter
|
||||
// may not be present, so drop any event whose source does not match the request.
|
||||
if (sourceName is not null && !string.Equals(mapped.SourceName, sourceName, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// One more matching event arriving once the cap is full means the result is
|
||||
// truncated — stop draining and flag it (Core.Abstractions-009).
|
||||
if (hasCap && collected.Count == maxEvents)
|
||||
{
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
collected.Add(mapped);
|
||||
}
|
||||
|
||||
var mapped = EventMapper.ToHistoricalEvents(events);
|
||||
RecordOutcome(success: true, error: null);
|
||||
return new HistoricalEventsResult(mapped, ContinuationPoint: null);
|
||||
// A non-null, opaque token signals truncation to the caller (Core.Abstractions-009).
|
||||
// The gateway has no resumable cursor, so the token's contents carry no paging state —
|
||||
// its presence alone is the "more events exist" signal. A fresh array per call keeps it
|
||||
// from being shared/mutated.
|
||||
return new HistoricalEventsResult(collected, truncated ? new byte[] { 0x01 } : null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user