4f4cdd05ec
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
83 lines
4.2 KiB
C#
83 lines
4.2 KiB
C#
using Microsoft.Data.Sqlite;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Replaces the central config DB's <c>ScriptedAlarmState</c> 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
|
|
/// <see cref="AlarmSfSchema"/>, which this mirrors.
|
|
/// </para>
|
|
/// <para>
|
|
/// Deliberately depends on nothing but <see cref="SqliteConnection"/> so it can be applied
|
|
/// to any connection — the host's <c>LocalDbSetup.OnReady</c> in production, and a bare
|
|
/// connection in a test — without dragging the DI graph along.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>The primary key is the alarm identity, and Save is an unconditional upsert onto it.</b>
|
|
/// 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>ActiveState has no column</b> — it is re-derived from the live predicate on startup,
|
|
/// so persisting it would only risk operators seeing a stale Active on restart. Likewise
|
|
/// <c>LastActiveUtc</c> / <c>LastClearedUtc</c> re-derive alongside it and get no columns.
|
|
/// <c>LastTransitionUtc</c> is carried by <c>updated_at_utc</c> — 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class AlarmConditionStateSchema
|
|
{
|
|
/// <summary>Table holding one condition-state row per scripted-alarm identity.</summary>
|
|
public const string StateTable = "alarm_condition_state";
|
|
|
|
/// <summary>
|
|
/// Creates the state table if it does not already exist. Idempotent.
|
|
/// </summary>
|
|
/// <param name="connection">
|
|
/// An already-open connection. <c>ILocalDb.CreateConnection()</c> hands out open,
|
|
/// pragma-configured connections — do not call <c>Open()</c> on one.
|
|
/// </param>
|
|
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();
|
|
}
|
|
}
|