using Microsoft.Extensions.Configuration; using ZB.MOM.WW.LocalDb; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; using ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache; namespace ZB.MOM.WW.OtOpcUa.Host.Configuration; /// /// The onReady callback handed to AddZbLocalDb: creates the deployment-cache and /// alarm store-and-forward tables and opts them into replication. /// /// /// /// Public rather than internal only so LocalDbSetupTests can drive the production /// callback directly. Initialising a test database from a hand-written copy of this schema /// would prove only that the test agrees with itself — the whole value of those tests is /// that they exercise this method. /// /// public static class LocalDbSetup { /// /// Initialises the local database: DDL first, then replication registration. /// /// /// /// THE ORDER IS LOAD-BEARING: DDL → RegisterReplicated → writes. /// RegisterReplicated is what installs the three AFTER triggers that capture /// changes into the oplog. Any row written before that call is never captured, so it /// never reaches the peer — silently, and permanently, because nothing ever revisits /// history. therefore runs last, after every /// registration — it writes rows, and they must be captured like any other write. /// /// /// The alarm buffer's tables are created unconditionally, regardless of whether this /// node has AlarmHistorian:Enabled set. An empty registered table costs three /// triggers and nothing else, whereas creating it lazily when the sink first appears /// would mean a node that enables the historian later writes rows before its triggers /// exist — which is precisely the silent-loss shape above. /// /// /// The freshly constructed local database. /// /// Application configuration, read by the legacy migrator for the pre-consolidation queue's /// path. Required rather than optional: an overload that silently skipped the migration /// would be one wiring mistake away from discarding a node's undelivered alarm history. /// public static void OnReady(ILocalDb db, IConfiguration configuration) { ArgumentNullException.ThrowIfNull(db); ArgumentNullException.ThrowIfNull(configuration); // CreateConnection() hands back an already-open, pragma-configured connection carrying the // zb_hlc_next() UDF the capture triggers need. Calling Open() on it would throw. using (var connection = db.CreateConnection()) { DeploymentCacheSchema.Apply(connection); AlarmSfSchema.Apply(connection); } db.RegisterReplicated(DeploymentCacheSchema.ArtifactsTable); db.RegisterReplicated(DeploymentCacheSchema.PointerTable); db.RegisterReplicated(AlarmSfSchema.EventsTable); // LAST, and only here. This is the one call in OnReady that writes rows. AlarmSfLegacyMigrator.Migrate(db, configuration); } }