9b30bdeb7a
SqlDriver implements IDriver / ITagDiscovery / IReadable / ISubscribable / IHostConnectivityProbe / IAsyncDisposable over the landed SqlPollReader, mirroring ModbusDriver. IWritable is deliberately absent: v1 is read-only structurally, and every discovered variable is SecurityClass=ViewOnly. The shell classifies poll outcomes because nothing below it does. The reader honours IReadable literally — it throws only when the database is unreachable and Bad-codes everything else — so a frozen database returns all-BadTimeout snapshots through a perfectly successful PollGroupEngine tick. Without ObservePollOutcome the driver would report Healthy while every value was Bad. Connection-class codes (BadTimeout / BadCommunicationError) degrade health and report the host Stopped; authoring-class codes (unresolvable RawPath, absent row, type mismatch) change nothing, so a tag typo never reports the database down. It deliberately does NOT synthesise an exception to earn engine backoff: the engine's exception path publishes nothing, which would cost clients the Bad quality the reader went out of its way to produce. Initialize builds the authored RawPath table first (pure, cannot fail; a malformed TagConfig is logged and skipped) then verifies liveness over one open-use-dispose connection bounded by wall clock as well as by token (the R2-01 lesson) — failure records Faulted and rethrows so DriverInstanceActor retries. The connection string is never logged: a credential-free server/database Endpoint is the only rendering that reaches a log, LastError, or the host status. DriverTypeNames.Sql is NOT added here — DriverTypeNamesGuardTests asserts bidirectional parity with registered factories, so the constant must land with the factory (Task 11). SqlDriver.DriverTypeName carries the string until then. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
57 lines
3.2 KiB
C#
57 lines
3.2 KiB
C#
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
|
|
|
|
/// <summary>
|
|
/// The typed form of a <c>Sql</c> driver instance's configuration — what
|
|
/// <see cref="SqlDriverConfigDto"/> becomes once the factory has applied its defaults (design §5.1).
|
|
/// Mirrors <c>ModbusDriverOptions</c>: the driver instance is constructed with these, and its
|
|
/// <see cref="SqlDriver.InitializeAsync"/> serves them rather than re-parsing the config JSON.
|
|
/// <para><b>No connection string here.</b> The resolved connection string is a separate constructor
|
|
/// argument, because it is a secret and these options are safe to log, diff and hold in a snapshot.</para>
|
|
/// </summary>
|
|
public sealed class SqlDriverOptions
|
|
{
|
|
/// <summary>
|
|
/// The authored raw tags this driver serves. The deploy artifact hands each authored raw <c>Tag</c>
|
|
/// as a <see cref="RawTagEntry"/> (RawPath identity + driver <c>TagConfig</c> blob); the driver maps
|
|
/// each through <see cref="SqlEquipmentTagParser.TryParse"/> into its RawPath → definition table.
|
|
/// A SQL source has no tag-discovery protocol — the driver serves exactly these.
|
|
/// </summary>
|
|
public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];
|
|
|
|
/// <summary>
|
|
/// Poll interval used when a subscriber does not ask for one (design §4: <c>defaultPollInterval</c>,
|
|
/// default 5 s). A subscriber that names an interval gets that interval, floored by
|
|
/// <see cref="PollGroupEngine.DefaultMinInterval"/>.
|
|
/// </summary>
|
|
public TimeSpan DefaultPollInterval { get; init; } = TimeSpan.FromSeconds(5);
|
|
|
|
/// <summary>
|
|
/// The client-side wall-clock ceiling on one group query (design §8.3, default 15 s). A breach
|
|
/// publishes <see cref="SqlStatusCodes.BadTimeout"/> for that group's tags.
|
|
/// </summary>
|
|
public TimeSpan OperationTimeout { get; init; } = TimeSpan.FromSeconds(15);
|
|
|
|
/// <summary>
|
|
/// The ADO.NET <c>CommandTimeout</c> server-side backstop (default 10 s), also the bound on the
|
|
/// Initialize-time liveness check.
|
|
/// <para>Authoring must keep <see cref="OperationTimeout"/> strictly greater than this (design §8.3);
|
|
/// inverted, the client-side abort always fires first and masks the backstop. <b>That rule is
|
|
/// enforced by config validation in the factory, not here</b> — the reader deliberately stays usable
|
|
/// with the order inverted so the frozen-database tests can prove the client-side bound fires.</para>
|
|
/// </summary>
|
|
public TimeSpan CommandTimeout { get; init; } = TimeSpan.FromSeconds(10);
|
|
|
|
/// <summary>Cap on concurrently executing group queries — and therefore on concurrently open connections.</summary>
|
|
public int MaxConcurrentGroups { get; init; } = 4;
|
|
|
|
/// <summary>
|
|
/// When <see langword="true"/>, a present row whose value cell is NULL publishes
|
|
/// <see cref="SqlStatusCodes.Bad"/> instead of the default <see cref="SqlStatusCodes.Uncertain"/>.
|
|
/// It never governs an absent row, which is always <see cref="SqlStatusCodes.BadNoData"/>.
|
|
/// </summary>
|
|
public bool NullIsBad { get; init; }
|
|
}
|