using Microsoft.Data.Sqlite; namespace ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; /// /// DDL for the Part 9 scripted-alarm condition state: one row per alarm identity holding the /// operator-supplied state (Enabled / Acked / Confirmed / Shelving) plus the ack/confirm audit /// trail and comment history. /// /// /// /// Replaces the central config DB's ScriptedAlarmState table as the node-local home /// for this state (per-cluster mesh Phase 4, which cuts the ConfigDb from driver-only /// nodes). Living in the consolidated LocalDb file is what lets the state replicate to the /// redundant pair peer, exactly like the alarm store-and-forward buffer — see /// , which this mirrors. /// /// /// Deliberately depends on nothing but so it can be applied /// to any connection — the host's LocalDbSetup.OnReady in production, and a bare /// connection in a test — without dragging the DI graph along. /// /// /// The primary key is the alarm identity, and Save is an unconditional upsert onto it. /// Convergence across the pair is last-writer-wins over the primary key, handled by the /// LocalDb replication HLC — so the store stacks no application-level last-write-wins on top, /// the same discipline the alarm-sf sink and the deployment pointer follow. /// /// /// ActiveState has no column — it is re-derived from the live predicate on startup, /// so persisting it would only risk operators seeing a stale Active on restart. Likewise /// LastActiveUtc / LastClearedUtc re-derive alongside it and get no columns. /// LastTransitionUtc is carried by updated_at_utc — the row-write timestamp is /// the last transition. Timestamps are round-trip ("O") TEXT so lexicographic order is /// chronological; nullable timestamps and audit fields are stored as NULL. /// /// public static class AlarmConditionStateSchema { /// Table holding one condition-state row per scripted-alarm identity. public const string StateTable = "alarm_condition_state"; /// /// Creates the state table if it does not already exist. Idempotent. /// /// /// An already-open connection. ILocalDb.CreateConnection() hands out open, /// pragma-configured connections — do not call Open() on one. /// public static void Apply(SqliteConnection connection) { ArgumentNullException.ThrowIfNull(connection); using var cmd = connection.CreateCommand(); // The four *_state columns are always written to a mapped, non-null string (Enabled/Disabled, // Acknowledged/Unacknowledged, Confirmed/Unconfirmed, Unshelved/OneShotShelved/TimedShelved), // so they are NOT NULL. comments_json is never null either — an empty trail is the literal // "[]". The audit user/comment/utc columns and shelving_expires_utc are NULL when the alarm // has no such history yet. updated_at_utc carries LastTransitionUtc (there is no dedicated // transition column). cmd.CommandText = """ CREATE TABLE IF NOT EXISTS alarm_condition_state ( scripted_alarm_id TEXT NOT NULL PRIMARY KEY, enabled_state TEXT NOT NULL, acked_state TEXT NOT NULL, confirmed_state TEXT NOT NULL, shelving_state TEXT NOT NULL, shelving_expires_utc TEXT NULL, last_ack_user TEXT NULL, last_ack_comment TEXT NULL, last_ack_utc TEXT NULL, last_confirm_user TEXT NULL, last_confirm_comment TEXT NULL, last_confirm_utc TEXT NULL, comments_json TEXT NOT NULL, updated_at_utc TEXT NOT NULL ); """; cmd.ExecuteNonQuery(); } }