3cc8069150
SqlDriverFactoryExtensions turns a deployed DriverConfig blob into a live SqlDriver, and SqlConnectionStringResolver resolves the authored connectionStringRef NAME from Sql__ConnectionStrings__<ref> so credentials never ride in a config blob (design §8.2). Three behaviours worth naming: - String-enum guard. JsonOptions carries JsonStringEnumConverter + camelCase, so `provider` parses whether authored as a name or an ordinal and always round-trips as the NAME — the systemic AdminUI defect where a page serialised an enum numerically against a string-typed DTO. - Config validation lands here because nothing below does it. operationTimeout must be STRICTLY greater than commandTimeout (design §8.3); the reader and the driver stay deliberately usable with the pair inverted so the frozen-database tests can prove the client-side bound fires. Non-positive timeouts / poll interval and maxConcurrentGroups < 1 are rejected too. - Credential hygiene. The resolved connection string reaches the provider and nothing else: messages name the ref, the environment variable, or SqlDriver.Endpoint's credential-free server/database rendering. Both a log-leak and an exception-leak test cover it. DTO changes: - Adds RawTags (List<RawTagEntry>), following the Modbus precedent — the deploy artifact delivers a driver's tags this way and the DTO had no way to receive them, so an authored Sql tag could never reach the driver. - Deletes SqlProbeDto + the `probe` key. It had no consumer: SqlDriver was specified without a background probe loop, and deliberately so — its IHostConnectivityProbe state is a by-product of the Initialize liveness check and of every poll, i.e. a statement about traffic that actually happened rather than a synthetic ping. On-demand connectivity is Task 10's SqlDriverProbe. Shipping a config key that silently does nothing is worse than not shipping it; UnmappedMemberHandling.Skip means a blob that still carries `probe` keeps parsing. allowWrites stays inert (SqlDriver implements no write capability) but an authored `true` now WARNS rather than passing silently — the flag cannot do harm, an operator who believes writes are enabled can. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
65 lines
3.7 KiB
C#
65 lines
3.7 KiB
C#
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
|
|
|
/// <summary>
|
|
/// The <c>DriverConfig</c> blob shape for a <c>Sql</c> driver instance (design §5.1). Deserialised by the
|
|
/// driver factory, which applies the documented defaults for every absent (null) field and maps the result
|
|
/// onto the driver's typed options.
|
|
/// <para><b>No connection string lives here.</b> Only <see cref="ConnectionStringRef"/> — a name resolved at
|
|
/// Initialize from the environment / secret store (e.g. <c>Sql__ConnectionStrings__MesStaging</c>) — so a
|
|
/// deployed artifact never carries database credentials.</para>
|
|
/// <para>Enum-valued fields are enum-typed (not strings) so a <c>JsonStringEnumConverter</c> round-trips
|
|
/// their <em>names</em> rather than their ordinals. The converter is applied by the factory's
|
|
/// <c>JsonSerializerOptions</c> (Task 9), not by an attribute on this DTO — mirroring the driver
|
|
/// enum-serialization trap that bit the AdminUI driver pages.</para>
|
|
/// </summary>
|
|
public sealed class SqlDriverConfigDto
|
|
{
|
|
/// <summary>Which ADO.NET provider/dialect backs this instance. Defaults to <see cref="SqlProvider.SqlServer"/> — the only provider v1 constructs.</summary>
|
|
public SqlProvider Provider { get; init; } = SqlProvider.SqlServer;
|
|
|
|
/// <summary>
|
|
/// Name of the connection string to resolve from the environment / secret store at Initialize —
|
|
/// <b>never</b> the connection string itself.
|
|
/// </summary>
|
|
public string? ConnectionStringRef { get; init; }
|
|
|
|
/// <summary>Poll interval applied to groups that do not carry their own. Absent ⇒ the factory default.</summary>
|
|
public TimeSpan? DefaultPollInterval { get; init; }
|
|
|
|
/// <summary>
|
|
/// Per-query wall-clock deadline enforced client-side (a breach surfaces <c>BadTimeout</c>). Must be
|
|
/// greater than <see cref="CommandTimeout"/>, which is only the server-side backstop (design §8.3).
|
|
/// </summary>
|
|
public TimeSpan? OperationTimeout { get; init; }
|
|
|
|
/// <summary>ADO.NET <c>CommandTimeout</c> backstop (seconds granularity server-side).</summary>
|
|
public TimeSpan? CommandTimeout { get; init; }
|
|
|
|
/// <summary>Cap on concurrently executing group queries — the connection-pool guard.</summary>
|
|
public int? MaxConcurrentGroups { get; init; }
|
|
|
|
/// <summary>When <see langword="true"/>, a NULL cell publishes Bad rather than the default Uncertain.</summary>
|
|
public bool? NullIsBad { get; init; }
|
|
|
|
/// <summary>
|
|
/// Master write kill-switch. <b>v1 is read-only and this field is inert</b> — the driver does not
|
|
/// implement <c>IWritable</c>, so no value here can produce a write. The factory <em>warns</em> when it
|
|
/// is authored <see langword="true"/> rather than failing the driver, since the flag cannot do harm but
|
|
/// an operator who believes writes are enabled can.
|
|
/// </summary>
|
|
public bool? AllowWrites { get; init; }
|
|
|
|
/// <summary>
|
|
/// The authored raw tags the deploy artifact delivers for this driver instance — RawPath identity plus
|
|
/// the driver <c>TagConfig</c> blob, the same shape every other driver receives. The factory copies
|
|
/// these onto the driver's options and the driver maps each through
|
|
/// <see cref="SqlEquipmentTagParser.TryParse"/> at Initialize.
|
|
/// <para>A SQL source has no tag-discovery protocol, so this list is the complete set of tags the
|
|
/// instance serves — an absent or empty list is a driver with nothing to poll, not a driver that will
|
|
/// find its tags later.</para>
|
|
/// </summary>
|
|
public List<RawTagEntry>? RawTags { get; init; }
|
|
}
|