using Microsoft.Data.Sqlite;
namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging;
///
/// DDL for the site_events table, extracted from so
/// it can be applied by whoever owns the database file.
///
/// Under LocalDb Phase 1 that owner is the Host's AddZbLocalDb onReady callback:
/// the table must exist before ILocalDb.RegisterReplicated installs its capture
/// triggers, and rows written before registration are never captured or snapshotted.
///
///
/// Schema change. The primary key is a TEXT GUID minted by the writer, replacing
/// the historical INTEGER PRIMARY KEY AUTOINCREMENT. This is required, not
/// cosmetic: replication resolves conflicts by last-writer-wins on the primary key, so
/// two site nodes each minting autoincrement id 1 for unrelated events would be treated
/// as one row and silently overwrite each other. GUID keys make the event log a pure
/// union across the pair. Consumers that relied on the id being monotonic — the keyset
/// paging cursor and the storage-cap purge — order by timestamp instead.
///
///
/// Deliberately depends only on Microsoft.Data.Sqlite, not on the LocalDb
/// library — the Host needs to apply this DDL to a LocalDb-managed connection, but
/// nothing about the schema itself is LocalDb-specific.
///
///
public static class SiteEventLogSchema
{
///
/// Creates the site_events table and its indexes when absent. Idempotent —
/// safe to run on every startup.
///
/// An open connection to the database that should hold the table.
public static void Apply(SqliteConnection connection)
{
ArgumentNullException.ThrowIfNull(connection);
// arch-review 2026-08-19 P4-6: this method used to also set
// "PRAGMA auto_vacuum = INCREMENTAL" here, on the theory that setting it right
// before CREATE TABLE would make it take effect on a fresh database. That was
// wrong in every real call path: SQLite only honors an auto_vacuum change on a
// database that has NO tables yet, and by the time this method runs — via the
// Host's AddZbLocalDb onReady callback (SiteLocalDbSetup.OnReady) OR via
// SiteEventLogger's own InitializeSchema fallback for directly-constructed
// instances — the consolidated file already holds LocalDb's own bookkeeping
// tables (SqliteLocalDb's constructor runs LocalDbSchema.EnsureCreated on its
// master connection before either caller ever gets a connection). So the pragma
// here had never once taken effect; it was a silent, permanent no-op. The real
// fix now lives in Host/SiteLocalDbSetup.cs
// (EnsureAutoVacuumOnFreshFile), which pre-creates a genuinely fresh LocalDb file
// with the pragma set BEFORE AddZbLocalDb's lazy singleton ever constructs
// SqliteLocalDb — and, being a Host-layer, one-time, pre-file-existence step, it
// helps ONLY brand-new site databases; every file created before this fix shipped
// stays auto_vacuum=NONE forever (see the PurgeByStorageCap remarks in
// EventLogPurgeService for the operational consequence).
using var cmd = connection.CreateCommand();
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS site_events (
id TEXT NOT NULL PRIMARY KEY,
timestamp TEXT NOT NULL,
event_type TEXT NOT NULL,
severity TEXT NOT NULL,
instance_id TEXT,
source TEXT NOT NULL,
message TEXT NOT NULL,
details TEXT
);
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON site_events(timestamp);
CREATE INDEX IF NOT EXISTS idx_events_type ON site_events(event_type);
CREATE INDEX IF NOT EXISTS idx_events_instance ON site_events(instance_id);
CREATE INDEX IF NOT EXISTS idx_events_severity ON site_events(severity);
""";
// The query service also supports keyword search via leading-wildcard
// LIKE on message/source. A leading-wildcard LIKE cannot use a B-tree
// index, so that path intentionally full-scans; severity/event_type/
// instance_id/timestamp filters above are all covered.
cmd.ExecuteNonQuery();
}
}