feat(localdb): rewire SiteEventLogger onto the consolidated site database
Completes Task 5a of the LocalDb Phase 1 adoption plan. The GUID-id half landed
with Task 2 (727fa48c) because leaving it out would have committed a writer that
could not satisfy site_events.id NOT NULL; this is the connection half, which
was blocked on Task 3.
SiteEventLogger owned a SqliteConnection built from SiteEventLogOptions
.DatabasePath. It now takes ILocalDb and gets that connection from
CreateConnection() - already open, pragma-configured, and carrying the
zb_hlc_next() UDF that site_events' capture triggers call. It still owns and
disposes the connection; WithConnection's signature and locking semantics are
untouched, so EventLogQueryService and EventLogPurgeService are unaffected.
The connectionStringOverride seam is deleted rather than preserved. site_events
is a replicated table, so a raw connection lacks the UDF and fails closed on
every insert - an override could only produce a logger that cannot write. It had
no callers outside this class (the connectionStringOverride hits elsewhere in the
tree belong to SqliteAuditWriter, a different type).
Tests: a shared TestLocalDb helper gives each fixture a real ILocalDb over a temp
file. Real rather than stubbed on purpose - a stub handing back a bare
SqliteConnection would pass today and fail closed the moment the table is
registered, which is exactly the silent-wiring failure mode this family has hit
three times. ServiceWiringTests' DI path also needed AddZbLocalDb, mirroring
SiteServiceRegistration.
Verified: build 0 warnings; SiteEventLogging 70/70, Host 294/294,
SiteRuntime 530/530.
Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Threading.Channels;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.LocalDb;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging;
|
||||
|
||||
@@ -40,29 +41,36 @@ public class SiteEventLogger : ISiteEventLogger, IDisposable
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the event logger, opens the SQLite connection, and starts the background writer loop.
|
||||
/// Initializes the event logger over the consolidated site database and starts the
|
||||
/// background writer loop.
|
||||
/// </summary>
|
||||
/// <param name="options">Site event log configuration (database path, retention settings).</param>
|
||||
/// <param name="options">Site event log configuration (retention settings, queue capacity).</param>
|
||||
/// <param name="logger">Logger for write-failure diagnostics.</param>
|
||||
/// <param name="connectionStringOverride">Optional connection string override; uses the configured path when null.</param>
|
||||
/// <param name="localDb">
|
||||
/// The consolidated site database. The connection it hands out is already open and
|
||||
/// carries the <c>zb_hlc_next()</c> UDF that <c>site_events</c>' capture triggers
|
||||
/// call. This logger owns the connection for its lifetime and disposes it.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The former <c>connectionStringOverride</c> seam is deliberately gone rather than
|
||||
/// preserved. <c>site_events</c> is a replicated table, so a raw connection lacking
|
||||
/// the UDF would fail closed on every insert — an override would only let a caller
|
||||
/// build a logger that cannot write. <c>SiteEventLogOptions.DatabasePath</c> is
|
||||
/// likewise no longer read: the location is <c>LocalDb:Path</c>.
|
||||
/// </remarks>
|
||||
public SiteEventLogger(
|
||||
IOptions<SiteEventLogOptions> options,
|
||||
ILogger<SiteEventLogger> logger,
|
||||
string? connectionStringOverride = null)
|
||||
ILocalDb localDb)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(localDb);
|
||||
|
||||
_logger = logger;
|
||||
|
||||
// Cache=Shared is a cross-connection optimisation
|
||||
// that lets multiple SqliteConnections share an in-process page cache.
|
||||
// This logger owns exactly one SqliteConnection and serialises all
|
||||
// access through _writeLock, so the mode is dormant — at best dead
|
||||
// configuration, at worst a small future foot-gun for any second
|
||||
// connection opened to the same file. A test path that genuinely
|
||||
// needs Cache=Shared can still inject it via connectionStringOverride.
|
||||
var connectionString = connectionStringOverride
|
||||
?? $"Data Source={options.Value.DatabasePath}";
|
||||
_connection = new SqliteConnection(connectionString);
|
||||
_connection.Open();
|
||||
// Already open — CreateConnection returns a live, pragma-configured,
|
||||
// UDF-registered connection. Calling Open() on it again would throw.
|
||||
_connection = localDb.CreateConnection();
|
||||
|
||||
InitializeSchema();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user