diff --git a/Lite.Tests/DismissedArchiveSidecarTests.cs b/Lite.Tests/DismissedArchiveSidecarTests.cs
new file mode 100644
index 0000000..53c61d5
--- /dev/null
+++ b/Lite.Tests/DismissedArchiveSidecarTests.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using DuckDB.NET.Data;
+using PerformanceMonitorLite.Database;
+using PerformanceMonitorLite.Tests.Helpers;
+using Xunit;
+
+namespace PerformanceMonitorLite.Tests;
+
+///
+/// Tests that the dismissed_archive_alerts sidecar table allows archived alerts
+/// to be dismissed, and that the archive view filters them out correctly.
+///
+public class DismissedArchiveSidecarTests : IDisposable
+{
+ private readonly string _tempDir;
+ private readonly string _dbPath;
+ private readonly TestAlertDataHelper _helper;
+
+ public DismissedArchiveSidecarTests()
+ {
+ _tempDir = Path.Combine(Path.GetTempPath(), "LiteTests_" + Guid.NewGuid().ToString("N")[..8]);
+ Directory.CreateDirectory(_tempDir);
+ _dbPath = Path.Combine(_tempDir, "test.duckdb");
+ _helper = new TestAlertDataHelper(_dbPath);
+ }
+
+ public void Dispose()
+ {
+ try
+ {
+ if (Directory.Exists(_tempDir))
+ Directory.Delete(_tempDir, recursive: true);
+ }
+ catch
+ {
+ /* Best-effort cleanup */
+ }
+ }
+
+ private async Task InitializeDatabaseAsync()
+ {
+ var initializer = new DuckDbInitializer(_dbPath);
+ await initializer.InitializeAsync();
+
+ var connection = new DuckDBConnection($"Data Source={_dbPath}");
+ await connection.OpenAsync(TestContext.Current.CancellationToken);
+ return connection;
+ }
+
+ [Fact]
+ public async Task SidecarTable_ExistsAfterInit()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT COUNT(1) FROM information_schema.tables WHERE table_name = 'dismissed_archive_alerts'";
+ var count = Convert.ToInt32(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+
+ Assert.Equal(1, count);
+ }
+
+ [Fact]
+ public async Task SidecarTable_HasCorrectColumns()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+SELECT column_name
+FROM information_schema.columns
+WHERE table_name = 'dismissed_archive_alerts'
+ORDER BY ordinal_position";
+
+ var columns = new List();
+ using var reader = await cmd.ExecuteReaderAsync(TestContext.Current.CancellationToken);
+ while (await reader.ReadAsync(TestContext.Current.CancellationToken))
+ columns.Add(reader.GetString(0));
+
+ Assert.Equal(new[] { "alert_time", "server_id", "metric_name", "dismissed_at" }, columns);
+ }
+
+ [Fact]
+ public async Task SidecarInsert_HidesArchivedAlertFromView()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ var alertTime = DateTime.UtcNow.AddDays(-14);
+ var archivedAlerts = new List
+ {
+ TestAlertDataHelper.CreateAlert(
+ alertTime: alertTime,
+ serverId: 1,
+ metricName: "High CPU",
+ serverName: "Server1")
+ };
+ await _helper.CreateArchivedAlertsParquetAsync(connection, archivedAlerts);
+ await _helper.RefreshArchiveViewsAsync();
+
+ // Verify alert is visible before sidecar insert
+ using var beforeCmd = connection.CreateCommand();
+ beforeCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE metric_name = 'High CPU'";
+ var beforeCount = Convert.ToInt64(await beforeCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(1, beforeCount);
+
+ // Insert into sidecar
+ using var insertCmd = connection.CreateCommand();
+ insertCmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+VALUES ($1, $2, $3)";
+ insertCmd.Parameters.Add(new DuckDBParameter { Value = alertTime });
+ insertCmd.Parameters.Add(new DuckDBParameter { Value = 1 });
+ insertCmd.Parameters.Add(new DuckDBParameter { Value = "High CPU" });
+ await insertCmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
+
+ // Verify alert is now hidden from view
+ using var afterCmd = connection.CreateCommand();
+ afterCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE metric_name = 'High CPU'";
+ var afterCount = Convert.ToInt64(await afterCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(0, afterCount);
+ }
+
+ [Fact]
+ public async Task SidecarInsert_DoesNotAffectLiveAlerts()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ // Insert a live alert
+ var liveTime = DateTime.UtcNow.AddHours(-1);
+ await _helper.InsertLiveAlertAsync(connection, liveTime, 1, "Server1", "Blocking");
+
+ // Insert an archived alert
+ var archiveTime = DateTime.UtcNow.AddDays(-14);
+ var archivedAlerts = new List
+ {
+ TestAlertDataHelper.CreateAlert(
+ alertTime: archiveTime,
+ serverId: 1,
+ metricName: "High CPU",
+ serverName: "Server1")
+ };
+ await _helper.CreateArchivedAlertsParquetAsync(connection, archivedAlerts);
+ await _helper.RefreshArchiveViewsAsync();
+
+ // Dismiss the archived alert via sidecar
+ using var insertCmd = connection.CreateCommand();
+ insertCmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+VALUES ($1, $2, $3)";
+ insertCmd.Parameters.Add(new DuckDBParameter { Value = archiveTime });
+ insertCmd.Parameters.Add(new DuckDBParameter { Value = 1 });
+ insertCmd.Parameters.Add(new DuckDBParameter { Value = "High CPU" });
+ await insertCmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
+
+ // Live alert should still be visible
+ using var checkCmd = connection.CreateCommand();
+ checkCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE metric_name = 'Blocking'";
+ var liveCount = Convert.ToInt64(await checkCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(1, liveCount);
+
+ // Archived alert should be hidden
+ using var archiveCmd = connection.CreateCommand();
+ archiveCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE metric_name = 'High CPU'";
+ var archiveCount = Convert.ToInt64(await archiveCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(0, archiveCount);
+ }
+
+ [Fact]
+ public async Task SidecarInsert_PreventsDuplicates()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ var alertTime = DateTime.UtcNow.AddDays(-14);
+
+ // Insert the same sidecar entry twice using the NOT EXISTS pattern
+ for (int i = 0; i < 2; i++)
+ {
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+SELECT $1, $2, $3
+WHERE NOT EXISTS (
+ SELECT 1 FROM dismissed_archive_alerts
+ WHERE alert_time = $1
+ AND server_id = $2
+ AND metric_name = $3
+)";
+ cmd.Parameters.Add(new DuckDBParameter { Value = alertTime });
+ cmd.Parameters.Add(new DuckDBParameter { Value = 1 });
+ cmd.Parameters.Add(new DuckDBParameter { Value = "High CPU" });
+ await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
+ }
+
+ // Verify only one row exists
+ using var countCmd = connection.CreateCommand();
+ countCmd.CommandText = "SELECT COUNT(1) FROM dismissed_archive_alerts";
+ var count = Convert.ToInt64(await countCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(1, count);
+ }
+
+ [Fact]
+ public async Task DismissAll_HandlesLiveAndArchivedAlerts()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ // Insert live alerts
+ var liveTime1 = DateTime.UtcNow.AddHours(-1);
+ var liveTime2 = DateTime.UtcNow.AddHours(-2);
+ await _helper.InsertLiveAlertAsync(connection, liveTime1, 1, "Server1", "High CPU");
+ await _helper.InsertLiveAlertAsync(connection, liveTime2, 1, "Server1", "Blocking");
+
+ // Create archived alerts
+ var archiveTime = DateTime.UtcNow.AddDays(-3);
+ var archivedAlerts = new List
+ {
+ TestAlertDataHelper.CreateAlert(
+ alertTime: archiveTime,
+ serverId: 1,
+ metricName: "Deadlock Detected",
+ serverName: "Server1")
+ };
+ await _helper.CreateArchivedAlertsParquetAsync(connection, archivedAlerts);
+ await _helper.RefreshArchiveViewsAsync();
+
+ // Verify 3 alerts visible
+ using var beforeCmd = connection.CreateCommand();
+ beforeCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE dismissed = FALSE";
+ var beforeCount = Convert.ToInt64(await beforeCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(3, beforeCount);
+
+ // Dismiss all live alerts
+ using var updateCmd = connection.CreateCommand();
+ updateCmd.CommandText = @"
+UPDATE config_alert_log
+SET dismissed = TRUE
+WHERE dismissed = FALSE";
+ var liveAffected = await updateCmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
+ Assert.Equal(2, liveAffected);
+
+ // Insert archived alert into sidecar (simulates what DismissAllVisibleAlertsAsync does)
+ using var sidecarCmd = connection.CreateCommand();
+ sidecarCmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+SELECT v.alert_time, v.server_id, v.metric_name
+FROM v_config_alert_log v
+WHERE v.dismissed = FALSE
+AND NOT EXISTS (
+ SELECT 1 FROM config_alert_log l
+ WHERE l.alert_time = v.alert_time
+ AND l.server_id = v.server_id
+ AND l.metric_name = v.metric_name
+)
+AND NOT EXISTS (
+ SELECT 1 FROM dismissed_archive_alerts d
+ WHERE d.alert_time = v.alert_time
+ AND d.server_id = v.server_id
+ AND d.metric_name = v.metric_name
+)";
+ var archivedAffected = await sidecarCmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
+ Assert.Equal(1, archivedAffected);
+
+ // All alerts should now be hidden from view
+ using var afterCmd = connection.CreateCommand();
+ afterCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE dismissed = FALSE";
+ var afterCount = Convert.ToInt64(await afterCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(0, afterCount);
+ }
+
+ [Fact]
+ public async Task SchemaVersion_IsUpdatedTo23()
+ {
+ using var connection = await InitializeDatabaseAsync();
+
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT MAX(version) FROM schema_version";
+ var version = Convert.ToInt32(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
+
+ Assert.Equal(23, version);
+ }
+}
diff --git a/Lite.Tests/DuckDbSchemaTests.cs b/Lite.Tests/DuckDbSchemaTests.cs
index a1b68f5..c0526f6 100644
--- a/Lite.Tests/DuckDbSchemaTests.cs
+++ b/Lite.Tests/DuckDbSchemaTests.cs
@@ -138,8 +138,8 @@ public void SchemaStatements_MatchTableCount()
foreach (var _ in Schema.GetAllTableStatements())
tableCount++;
- /* 28 tables from Schema (schema_version is created separately by DuckDbInitializer) */
- Assert.Equal(28, tableCount);
+ /* 29 tables from Schema (schema_version is created separately by DuckDbInitializer) */
+ Assert.Equal(29, tableCount);
}
[Fact]
diff --git a/Lite/Database/DuckDbInitializer.cs b/Lite/Database/DuckDbInitializer.cs
index 5bbca8d..e7291e7 100644
--- a/Lite/Database/DuckDbInitializer.cs
+++ b/Lite/Database/DuckDbInitializer.cs
@@ -86,7 +86,7 @@ public void Dispose()
///
/// Current schema version. Increment this when schema changes require table rebuilds.
///
- internal const int CurrentSchemaVersion = 22;
+ internal const int CurrentSchemaVersion = 23;
private readonly string _archivePath;
@@ -599,6 +599,21 @@ New tables only — no existing table changes needed. Tables created by
throw;
}
}
+
+ if (fromVersion < 23)
+ {
+ _logger?.LogInformation("Running migration to v23: adding dismissed_archive_alerts sidecar table");
+ try
+ {
+ await ExecuteNonQueryAsync(connection, Schema.CreateDismissedArchiveAlertsTable);
+ await ExecuteNonQueryAsync(connection, Schema.CreateDismissedArchiveAlertsIndex);
+ }
+ catch (Exception ex)
+ {
+ _logger?.LogError(ex, "Migration to v23 failed");
+ throw;
+ }
+ }
}
///
@@ -686,9 +701,22 @@ public async Task CreateArchiveViewsAsync()
{
var globPath = parquetGlob.Replace("\\", "/");
if (table == "config_alert_log")
- viewSql = $"CREATE OR REPLACE VIEW v_{table} AS SELECT *, 'live' AS source FROM {table} UNION ALL BY NAME SELECT *, 'archive' AS source FROM read_parquet('{globPath}', union_by_name=true)";
+ {
+ viewSql = $@"CREATE OR REPLACE VIEW v_{table} AS
+SELECT *, 'live' AS source FROM {table}
+UNION ALL BY NAME
+SELECT *, 'archive' AS source FROM read_parquet('{globPath}', union_by_name=true) p
+WHERE NOT EXISTS (
+ SELECT 1 FROM dismissed_archive_alerts d
+ WHERE d.alert_time = p.alert_time
+ AND d.server_id = p.server_id
+ AND d.metric_name = p.metric_name
+)";
+ }
else
+ {
viewSql = $"CREATE OR REPLACE VIEW v_{table} AS SELECT * FROM {table} UNION ALL BY NAME SELECT * FROM read_parquet('{globPath}', union_by_name=true)";
+ }
}
else
{
diff --git a/Lite/Database/Schema.cs b/Lite/Database/Schema.cs
index 41aae34..09ccc1a 100644
--- a/Lite/Database/Schema.cs
+++ b/Lite/Database/Schema.cs
@@ -1,765 +1,779 @@
-using System.Collections.Generic;
-
-namespace PerformanceMonitorLite.Database;
-
-///
-/// Contains all DuckDB table schema definitions as SQL constants.
-///
-public static class Schema
-{
- public const string CreateServersTable = @"
-CREATE TABLE IF NOT EXISTS servers (
- server_id INTEGER PRIMARY KEY,
- server_name VARCHAR NOT NULL,
- display_name VARCHAR,
- use_windows_auth BOOLEAN NOT NULL DEFAULT TRUE,
- username VARCHAR,
- is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
- created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-)";
-
- public const string CreateCollectionScheduleTable = @"
-CREATE TABLE IF NOT EXISTS collection_schedule (
- schedule_id INTEGER PRIMARY KEY,
- collector_name VARCHAR NOT NULL UNIQUE,
- enabled BOOLEAN NOT NULL DEFAULT TRUE,
- frequency_minutes INTEGER NOT NULL DEFAULT 15,
- last_run_time TIMESTAMP,
- next_run_time TIMESTAMP,
- max_duration_minutes INTEGER DEFAULT 5,
- retention_days INTEGER DEFAULT 30,
- description VARCHAR,
- created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-)";
-
- public const string CreateCollectionLogTable = @"
-CREATE TABLE IF NOT EXISTS collection_log (
- log_id BIGINT PRIMARY KEY,
- server_id INTEGER NOT NULL,
- server_name VARCHAR,
- collector_name VARCHAR NOT NULL,
- collection_time TIMESTAMP NOT NULL,
- duration_ms INTEGER,
- status VARCHAR NOT NULL,
- error_message VARCHAR,
- rows_collected INTEGER,
- sql_duration_ms INTEGER,
- duckdb_duration_ms INTEGER
-)";
-
- public const string CreateWaitStatsTable = @"
-CREATE TABLE IF NOT EXISTS wait_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- wait_type VARCHAR NOT NULL,
- waiting_tasks_count BIGINT,
- wait_time_ms BIGINT,
- signal_wait_time_ms BIGINT,
- delta_waiting_tasks BIGINT,
- delta_wait_time_ms BIGINT,
- delta_signal_wait_time_ms BIGINT
-)";
-
- public const string CreateQueryStatsTable = @"
-CREATE TABLE IF NOT EXISTS query_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR,
- query_hash VARCHAR,
- query_plan_hash VARCHAR,
- creation_time TIMESTAMP,
- last_execution_time TIMESTAMP,
- execution_count BIGINT,
- total_worker_time BIGINT,
- total_elapsed_time BIGINT,
- total_logical_reads BIGINT,
- total_logical_writes BIGINT,
- total_physical_reads BIGINT,
- total_clr_time BIGINT,
- total_rows BIGINT,
- total_spills BIGINT,
- min_worker_time BIGINT,
- max_worker_time BIGINT,
- min_elapsed_time BIGINT,
- max_elapsed_time BIGINT,
- min_physical_reads BIGINT,
- max_physical_reads BIGINT,
- min_rows BIGINT,
- max_rows BIGINT,
- min_dop BIGINT,
- max_dop BIGINT,
- min_grant_kb BIGINT,
- max_grant_kb BIGINT,
- min_used_grant_kb BIGINT,
- max_used_grant_kb BIGINT,
- min_ideal_grant_kb BIGINT,
- max_ideal_grant_kb BIGINT,
- min_reserved_threads BIGINT,
- max_reserved_threads BIGINT,
- min_used_threads BIGINT,
- max_used_threads BIGINT,
- min_spills BIGINT,
- max_spills BIGINT,
- query_text VARCHAR,
- query_plan_xml VARCHAR,
- sql_handle VARCHAR,
- plan_handle VARCHAR,
- delta_execution_count BIGINT,
- delta_worker_time BIGINT,
- delta_elapsed_time BIGINT,
- delta_logical_reads BIGINT,
- delta_logical_writes BIGINT,
- delta_physical_reads BIGINT,
- delta_rows BIGINT,
- delta_spills BIGINT
-)";
-
- public const string CreateCpuUtilizationStatsTable = @"
-CREATE TABLE IF NOT EXISTS cpu_utilization_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- sample_time TIMESTAMP NOT NULL,
- sqlserver_cpu_utilization INTEGER,
- other_process_cpu_utilization INTEGER
-)";
-
- public const string CreateFileIoStatsTable = @"
-CREATE TABLE IF NOT EXISTS file_io_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR,
- file_name VARCHAR,
- file_type VARCHAR,
- physical_name VARCHAR,
- size_mb DECIMAL(18,2),
- num_of_reads BIGINT,
- num_of_writes BIGINT,
- read_bytes BIGINT,
- write_bytes BIGINT,
- io_stall_read_ms BIGINT,
- io_stall_write_ms BIGINT,
- io_stall_queued_read_ms BIGINT,
- io_stall_queued_write_ms BIGINT,
- delta_reads BIGINT,
- delta_writes BIGINT,
- delta_read_bytes BIGINT,
- delta_write_bytes BIGINT,
- delta_stall_read_ms BIGINT,
- delta_stall_write_ms BIGINT,
- delta_stall_queued_read_ms BIGINT,
- delta_stall_queued_write_ms BIGINT
-)";
-
- public const string CreateMemoryStatsTable = @"
-CREATE TABLE IF NOT EXISTS memory_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- total_physical_memory_mb DECIMAL(18,2),
- available_physical_memory_mb DECIMAL(18,2),
- total_page_file_mb DECIMAL(18,2),
- available_page_file_mb DECIMAL(18,2),
- system_memory_state VARCHAR,
- sql_memory_model VARCHAR,
- target_server_memory_mb DECIMAL(18,2),
- total_server_memory_mb DECIMAL(18,2),
- buffer_pool_mb DECIMAL(18,2),
- plan_cache_mb DECIMAL(18,2),
- max_workers_count INTEGER,
- current_workers_count INTEGER
-)";
-
- public const string CreateMemoryClerksTable = @"
-CREATE TABLE IF NOT EXISTS memory_clerks (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- clerk_type VARCHAR NOT NULL,
- memory_mb DECIMAL(18,2)
-)";
-
- public const string CreateDeadlocksTable = @"
-CREATE TABLE IF NOT EXISTS deadlocks (
- deadlock_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- deadlock_time TIMESTAMP,
- victim_process_id VARCHAR,
- victim_sql_text VARCHAR,
- deadlock_graph_xml VARCHAR
-)";
-
- public const string CreateProcedureStatsTable = @"
-CREATE TABLE IF NOT EXISTS procedure_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR,
- schema_name VARCHAR,
- object_name VARCHAR,
- object_type VARCHAR,
- cached_time TIMESTAMP,
- last_execution_time TIMESTAMP,
- execution_count BIGINT,
- total_worker_time BIGINT,
- total_elapsed_time BIGINT,
- total_logical_reads BIGINT,
- total_physical_reads BIGINT,
- total_logical_writes BIGINT,
- min_worker_time BIGINT,
- max_worker_time BIGINT,
- min_elapsed_time BIGINT,
- max_elapsed_time BIGINT,
- min_logical_reads BIGINT,
- max_logical_reads BIGINT,
- min_physical_reads BIGINT,
- max_physical_reads BIGINT,
- min_logical_writes BIGINT,
- max_logical_writes BIGINT,
- total_spills BIGINT,
- min_spills BIGINT,
- max_spills BIGINT,
- sql_handle VARCHAR,
- plan_handle VARCHAR,
- delta_execution_count BIGINT,
- delta_worker_time BIGINT,
- delta_elapsed_time BIGINT,
- delta_logical_reads BIGINT,
- delta_logical_writes BIGINT,
- delta_physical_reads BIGINT
-)";
-
- public const string CreateQueryStoreStatsTable = @"
-CREATE TABLE IF NOT EXISTS query_store_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR NOT NULL,
- query_id BIGINT,
- plan_id BIGINT,
- execution_type_desc VARCHAR,
- first_execution_time TIMESTAMP,
- last_execution_time TIMESTAMP,
- module_name VARCHAR,
- query_text VARCHAR,
- query_hash VARCHAR,
- execution_count BIGINT,
- avg_duration_us BIGINT,
- min_duration_us BIGINT,
- max_duration_us BIGINT,
- avg_cpu_time_us BIGINT,
- min_cpu_time_us BIGINT,
- max_cpu_time_us BIGINT,
- avg_logical_io_reads BIGINT,
- min_logical_io_reads BIGINT,
- max_logical_io_reads BIGINT,
- avg_logical_io_writes BIGINT,
- min_logical_io_writes BIGINT,
- max_logical_io_writes BIGINT,
- avg_physical_io_reads BIGINT,
- min_physical_io_reads BIGINT,
- max_physical_io_reads BIGINT,
- avg_clr_time_us BIGINT,
- min_clr_time_us BIGINT,
- max_clr_time_us BIGINT,
- min_dop BIGINT,
- max_dop BIGINT,
- avg_query_max_used_memory BIGINT,
- min_query_max_used_memory BIGINT,
- max_query_max_used_memory BIGINT,
- avg_rowcount BIGINT,
- min_rowcount BIGINT,
- max_rowcount BIGINT,
- avg_num_physical_io_reads BIGINT,
- min_num_physical_io_reads BIGINT,
- max_num_physical_io_reads BIGINT,
- avg_log_bytes_used BIGINT,
- min_log_bytes_used BIGINT,
- max_log_bytes_used BIGINT,
- avg_tempdb_space_used BIGINT,
- min_tempdb_space_used BIGINT,
- max_tempdb_space_used BIGINT,
- plan_type VARCHAR,
- plan_forcing_type VARCHAR,
- is_forced_plan BOOLEAN,
- force_failure_count BIGINT,
- last_force_failure_reason VARCHAR,
- compatibility_level INTEGER,
- query_plan_text VARCHAR,
- query_plan_hash VARCHAR
-)";
-
- public const string CreateQuerySnapshotsTable = @"
-CREATE TABLE IF NOT EXISTS query_snapshots (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- session_id INTEGER,
- database_name VARCHAR,
- elapsed_time_formatted VARCHAR,
- query_text VARCHAR,
- query_plan VARCHAR,
- live_query_plan VARCHAR,
- status VARCHAR,
- blocking_session_id INTEGER,
- wait_type VARCHAR,
- wait_time_ms BIGINT,
- wait_resource VARCHAR,
- cpu_time_ms BIGINT,
- total_elapsed_time_ms BIGINT,
- reads BIGINT,
- writes BIGINT,
- logical_reads BIGINT,
- granted_query_memory_gb DECIMAL(18,2),
- transaction_isolation_level VARCHAR,
- dop INTEGER,
- parallel_worker_count INTEGER,
- login_name VARCHAR,
- host_name VARCHAR,
- program_name VARCHAR,
- open_transaction_count INTEGER,
- percent_complete DECIMAL(5,2)
-)";
-
- public const string CreateTempdbStatsTable = @"
-CREATE TABLE IF NOT EXISTS tempdb_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- user_object_reserved_mb DECIMAL(18,2),
- internal_object_reserved_mb DECIMAL(18,2),
- version_store_reserved_mb DECIMAL(18,2),
- total_reserved_mb DECIMAL(18,2),
- unallocated_mb DECIMAL(18,2),
- total_sessions_using_tempdb BIGINT,
- top_session_id INTEGER,
- top_session_tempdb_mb DECIMAL(18,2)
-)";
-
- public const string CreatePerfmonStatsTable = @"
-CREATE TABLE IF NOT EXISTS perfmon_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- object_name VARCHAR NOT NULL,
- counter_name VARCHAR NOT NULL,
- instance_name VARCHAR,
- cntr_value BIGINT,
- delta_cntr_value BIGINT,
- sample_interval_seconds INTEGER
-)";
-
- public const string CreateServerConfigTable = @"
-CREATE TABLE IF NOT EXISTS server_config (
- config_id BIGINT PRIMARY KEY,
- capture_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- configuration_name VARCHAR NOT NULL,
- value_configured BIGINT,
- value_in_use BIGINT,
- is_dynamic BOOLEAN,
- is_advanced BOOLEAN
-)";
-
- public const string CreateMemoryGrantStatsTable = @"
-CREATE TABLE IF NOT EXISTS memory_grant_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- resource_semaphore_id SMALLINT,
- pool_id INTEGER,
- target_memory_mb DECIMAL(18,2),
- max_target_memory_mb DECIMAL(18,2),
- total_memory_mb DECIMAL(18,2),
- available_memory_mb DECIMAL(18,2),
- granted_memory_mb DECIMAL(18,2),
- used_memory_mb DECIMAL(18,2),
- grantee_count INTEGER,
- waiter_count INTEGER,
- timeout_error_count BIGINT,
- forced_grant_count BIGINT,
- timeout_error_count_delta BIGINT,
- forced_grant_count_delta BIGINT
-)";
-
- public const string CreateWaitingTasksTable = @"
-CREATE TABLE IF NOT EXISTS waiting_tasks (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- session_id INTEGER,
- wait_type VARCHAR,
- wait_duration_ms BIGINT,
- blocking_session_id INTEGER,
- resource_description VARCHAR,
- database_name VARCHAR
-)";
-
- public const string CreateBlockedProcessReportsTable = @"
-CREATE TABLE IF NOT EXISTS blocked_process_reports (
- blocked_report_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- event_time TIMESTAMP,
- database_name VARCHAR,
- blocked_spid INTEGER,
- blocked_ecid INTEGER,
- blocking_spid INTEGER,
- blocking_ecid INTEGER,
- wait_time_ms BIGINT,
- wait_resource VARCHAR,
- lock_mode VARCHAR,
- blocked_status VARCHAR,
- blocked_isolation_level VARCHAR,
- blocked_log_used BIGINT,
- blocked_transaction_count INTEGER,
- blocked_client_app VARCHAR,
- blocked_host_name VARCHAR,
- blocked_login_name VARCHAR,
- blocked_sql_text VARCHAR,
- blocking_status VARCHAR,
- blocking_isolation_level VARCHAR,
- blocking_client_app VARCHAR,
- blocking_host_name VARCHAR,
- blocking_login_name VARCHAR,
- blocking_sql_text VARCHAR,
- blocked_transaction_name VARCHAR,
- blocking_transaction_name VARCHAR,
- blocked_last_tran_started TIMESTAMP,
- blocking_last_tran_started TIMESTAMP,
- blocked_last_batch_started TIMESTAMP,
- blocking_last_batch_started TIMESTAMP,
- blocked_last_batch_completed TIMESTAMP,
- blocking_last_batch_completed TIMESTAMP,
- blocked_priority INTEGER,
- blocking_priority INTEGER,
- blocked_process_report_xml VARCHAR
-)";
-
- public const string CreateDatabaseConfigTable = @"
-CREATE TABLE IF NOT EXISTS database_config (
- config_id BIGINT PRIMARY KEY,
- capture_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR NOT NULL,
- state_desc VARCHAR,
- compatibility_level INTEGER,
- collation_name VARCHAR,
- recovery_model VARCHAR,
- is_read_only BOOLEAN,
- is_auto_close_on BOOLEAN,
- is_auto_shrink_on BOOLEAN,
- is_auto_create_stats_on BOOLEAN,
- is_auto_update_stats_on BOOLEAN,
- is_auto_update_stats_async_on BOOLEAN,
- is_read_committed_snapshot_on BOOLEAN,
- snapshot_isolation_state VARCHAR,
- is_parameterization_forced BOOLEAN,
- is_query_store_on BOOLEAN,
- is_encrypted BOOLEAN,
- is_trustworthy_on BOOLEAN,
- is_db_chaining_on BOOLEAN,
- is_broker_enabled BOOLEAN,
- is_cdc_enabled BOOLEAN,
- is_mixed_page_allocation_on BOOLEAN,
- log_reuse_wait_desc VARCHAR,
- page_verify_option VARCHAR,
- target_recovery_time_seconds INTEGER,
- delayed_durability VARCHAR,
- is_accelerated_database_recovery_on BOOLEAN,
- is_memory_optimized_enabled BOOLEAN,
- is_optimized_locking_on BOOLEAN
-)";
-
- // Index definitions
- public const string CreateWaitStatsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_wait_stats_time ON wait_stats(server_id, collection_time)";
-
- public const string CreateQueryStatsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_query_stats_time ON query_stats(server_id, collection_time)";
-
- public const string CreateProcedureStatsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_procedure_stats_time ON procedure_stats(server_id, collection_time)";
-
- public const string CreateQueryStoreIndex = @"
-CREATE INDEX IF NOT EXISTS idx_query_store_time ON query_store_stats(server_id, collection_time)";
-
- public const string CreateQuerySnapshotsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_query_snapshots_time ON query_snapshots(server_id, collection_time)";
-
- public const string CreateCpuIndex = @"
-CREATE INDEX IF NOT EXISTS idx_cpu_time ON cpu_utilization_stats(server_id, collection_time)";
-
- public const string CreateFileIoIndex = @"
-CREATE INDEX IF NOT EXISTS idx_file_io_time ON file_io_stats(server_id, collection_time)";
-
- public const string CreateMemoryIndex = @"
-CREATE INDEX IF NOT EXISTS idx_memory_time ON memory_stats(server_id, collection_time)";
-
- public const string CreateTempdbIndex = @"
-CREATE INDEX IF NOT EXISTS idx_tempdb_time ON tempdb_stats(server_id, collection_time)";
-
- public const string CreatePerfmonIndex = @"
-CREATE INDEX IF NOT EXISTS idx_perfmon_time ON perfmon_stats(server_id, collection_time)";
-
- public const string CreateDeadlocksIndex = @"
-CREATE INDEX IF NOT EXISTS idx_deadlocks_time ON deadlocks(server_id, collection_time)";
-
- public const string CreateCollectionLogIndex = @"
-CREATE INDEX IF NOT EXISTS idx_collection_log_time ON collection_log(server_id, collection_time)";
-
- public const string CreateMemoryGrantStatsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_memory_grant_stats_time ON memory_grant_stats(server_id, collection_time)";
-
- public const string CreateWaitingTasksIndex = @"
-CREATE INDEX IF NOT EXISTS idx_waiting_tasks_time ON waiting_tasks(server_id, collection_time)";
-
- public const string CreateBlockedProcessReportsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_blocked_process_reports_time ON blocked_process_reports(server_id, collection_time)";
-
- public const string CreateMemoryClerksIndex = @"
-CREATE INDEX IF NOT EXISTS idx_memory_clerks_time ON memory_clerks(server_id, collection_time)";
-
- public const string CreateDatabaseScopedConfigTable = @"
-CREATE TABLE IF NOT EXISTS database_scoped_config (
- config_id BIGINT PRIMARY KEY,
- capture_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR NOT NULL,
- configuration_name VARCHAR NOT NULL,
- value VARCHAR,
- value_for_secondary VARCHAR
-)";
-
- public const string CreateDatabaseScopedConfigIndex = @"
-CREATE INDEX IF NOT EXISTS idx_database_scoped_config_time ON database_scoped_config(server_id, capture_time)";
-
- public const string CreateTraceFlagsTable = @"
-CREATE TABLE IF NOT EXISTS trace_flags (
- config_id BIGINT PRIMARY KEY,
- capture_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- trace_flag INTEGER NOT NULL,
- status BOOLEAN NOT NULL,
- is_global BOOLEAN NOT NULL,
- is_session BOOLEAN NOT NULL
-)";
-
- public const string CreateTraceFlagsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_trace_flags_time ON trace_flags(server_id, capture_time)";
-
- public const string CreateRunningJobsTable = @"
-CREATE TABLE IF NOT EXISTS running_jobs (
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- job_name VARCHAR NOT NULL,
- job_id VARCHAR NOT NULL,
- job_enabled BOOLEAN NOT NULL,
- start_time TIMESTAMP NOT NULL,
- current_duration_seconds BIGINT NOT NULL,
- avg_duration_seconds BIGINT NOT NULL,
- p95_duration_seconds BIGINT NOT NULL,
- successful_run_count BIGINT NOT NULL,
- is_running_long BOOLEAN NOT NULL,
- percent_of_average DECIMAL(10,1)
-)";
-
- public const string CreateRunningJobsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_running_jobs_time ON running_jobs(server_id, collection_time)";
-
- public const string CreateDatabaseSizeStatsTable = @"
-CREATE TABLE IF NOT EXISTS database_size_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- database_name VARCHAR NOT NULL,
- database_id INTEGER NOT NULL,
- file_id INTEGER NOT NULL,
- file_type_desc VARCHAR NOT NULL,
- file_name VARCHAR NOT NULL,
- physical_name VARCHAR NOT NULL,
- total_size_mb DECIMAL(19,2) NOT NULL,
- used_size_mb DECIMAL(19,2),
- auto_growth_mb DECIMAL(19,2),
- max_size_mb DECIMAL(19,2),
- recovery_model_desc VARCHAR,
- compatibility_level INTEGER,
- state_desc VARCHAR,
- volume_mount_point VARCHAR,
- volume_total_mb DECIMAL(19,2),
- volume_free_mb DECIMAL(19,2),
- is_percent_growth BOOLEAN,
- growth_pct INTEGER,
- vlf_count INTEGER
-)";
-
- public const string CreateDatabaseSizeStatsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_database_size_stats_time ON database_size_stats(server_id, collection_time)";
-
- public const string CreateServerPropertiesTable = @"
-CREATE TABLE IF NOT EXISTS server_properties (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- edition VARCHAR NOT NULL,
- product_version VARCHAR NOT NULL,
- product_level VARCHAR NOT NULL,
- product_update_level VARCHAR,
- engine_edition INTEGER NOT NULL,
- cpu_count INTEGER NOT NULL,
- hyperthread_ratio INTEGER NOT NULL,
- physical_memory_mb BIGINT NOT NULL,
- socket_count INTEGER,
- cores_per_socket INTEGER,
- is_hadr_enabled BOOLEAN,
- is_clustered BOOLEAN,
- enterprise_features VARCHAR,
- service_objective VARCHAR
-)";
-
- public const string CreateServerPropertiesIndex = @"
-CREATE INDEX IF NOT EXISTS idx_server_properties_time ON server_properties(server_id, collection_time)";
-
- public const string CreateSessionStatsTable = @"
-CREATE TABLE IF NOT EXISTS session_stats (
- collection_id BIGINT PRIMARY KEY,
- collection_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- program_name VARCHAR NOT NULL,
- connection_count BIGINT NOT NULL,
- running_count INTEGER NOT NULL,
- sleeping_count INTEGER NOT NULL,
- dormant_count INTEGER NOT NULL,
- total_cpu_time_ms BIGINT,
- total_reads BIGINT,
- total_writes BIGINT,
- total_logical_reads BIGINT
-)";
-
- public const string CreateSessionStatsIndex = @"
-CREATE INDEX IF NOT EXISTS idx_session_stats_time ON session_stats(server_id, collection_time)";
-
- public const string CreateAlertLogTable = @"
-CREATE TABLE IF NOT EXISTS config_alert_log (
- alert_time TIMESTAMP NOT NULL,
- server_id INTEGER NOT NULL,
- server_name VARCHAR NOT NULL,
- metric_name VARCHAR NOT NULL,
- current_value DOUBLE NOT NULL,
- threshold_value DOUBLE NOT NULL,
- alert_sent BOOLEAN NOT NULL DEFAULT false,
- notification_type VARCHAR NOT NULL DEFAULT 'tray',
- send_error VARCHAR,
- dismissed BOOLEAN NOT NULL DEFAULT false,
- muted BOOLEAN NOT NULL DEFAULT false,
- detail_text VARCHAR
-)";
-
- public const string CreateMuteRulesTable = @"
-CREATE TABLE IF NOT EXISTS config_mute_rules (
- id VARCHAR NOT NULL PRIMARY KEY,
- enabled BOOLEAN NOT NULL DEFAULT true,
- created_at_utc TIMESTAMP NOT NULL,
- expires_at_utc TIMESTAMP,
- reason VARCHAR,
- server_name VARCHAR,
- metric_name VARCHAR,
- database_pattern VARCHAR,
- query_text_pattern VARCHAR,
- wait_type_pattern VARCHAR,
- job_name_pattern VARCHAR
-)";
-
- ///
- /// Returns all table creation statements in order.
- ///
- public static IEnumerable GetAllTableStatements()
- {
- yield return CreateServersTable;
- yield return CreateCollectionScheduleTable;
- yield return CreateCollectionLogTable;
- yield return CreateWaitStatsTable;
- yield return CreateQueryStatsTable;
- yield return CreateCpuUtilizationStatsTable;
- yield return CreateFileIoStatsTable;
- yield return CreateMemoryStatsTable;
- yield return CreateMemoryClerksTable;
- yield return CreateDeadlocksTable;
- yield return CreateProcedureStatsTable;
- yield return CreateQueryStoreStatsTable;
- yield return CreateQuerySnapshotsTable;
- yield return CreateTempdbStatsTable;
- yield return CreatePerfmonStatsTable;
- yield return CreateServerConfigTable;
- yield return CreateDatabaseConfigTable;
- yield return CreateMemoryGrantStatsTable;
- yield return CreateWaitingTasksTable;
- yield return CreateBlockedProcessReportsTable;
- yield return CreateDatabaseScopedConfigTable;
- yield return CreateTraceFlagsTable;
- yield return CreateRunningJobsTable;
- yield return CreateDatabaseSizeStatsTable;
- yield return CreateServerPropertiesTable;
- yield return CreateSessionStatsTable;
- yield return CreateAlertLogTable;
- yield return CreateMuteRulesTable;
- }
-
- ///
- /// Returns all index creation statements.
- ///
- public static IEnumerable GetAllIndexStatements()
- {
- yield return CreateWaitStatsIndex;
- yield return CreateQueryStatsIndex;
- yield return CreateProcedureStatsIndex;
- yield return CreateQueryStoreIndex;
- yield return CreateQuerySnapshotsIndex;
- yield return CreateCpuIndex;
- yield return CreateFileIoIndex;
- yield return CreateMemoryIndex;
- yield return CreateTempdbIndex;
- yield return CreatePerfmonIndex;
- yield return CreateDeadlocksIndex;
- yield return CreateCollectionLogIndex;
- yield return CreateMemoryGrantStatsIndex;
- yield return CreateWaitingTasksIndex;
- yield return CreateBlockedProcessReportsIndex;
- yield return CreateMemoryClerksIndex;
- yield return CreateDatabaseScopedConfigIndex;
- yield return CreateTraceFlagsIndex;
- yield return CreateRunningJobsIndex;
- yield return CreateDatabaseSizeStatsIndex;
- yield return CreateServerPropertiesIndex;
- yield return CreateSessionStatsIndex;
- }
-}
+using System.Collections.Generic;
+
+namespace PerformanceMonitorLite.Database;
+
+///
+/// Contains all DuckDB table schema definitions as SQL constants.
+///
+public static class Schema
+{
+ public const string CreateServersTable = @"
+CREATE TABLE IF NOT EXISTS servers (
+ server_id INTEGER PRIMARY KEY,
+ server_name VARCHAR NOT NULL,
+ display_name VARCHAR,
+ use_windows_auth BOOLEAN NOT NULL DEFAULT TRUE,
+ username VARCHAR,
+ is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+)";
+
+ public const string CreateCollectionScheduleTable = @"
+CREATE TABLE IF NOT EXISTS collection_schedule (
+ schedule_id INTEGER PRIMARY KEY,
+ collector_name VARCHAR NOT NULL UNIQUE,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ frequency_minutes INTEGER NOT NULL DEFAULT 15,
+ last_run_time TIMESTAMP,
+ next_run_time TIMESTAMP,
+ max_duration_minutes INTEGER DEFAULT 5,
+ retention_days INTEGER DEFAULT 30,
+ description VARCHAR,
+ created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+)";
+
+ public const string CreateCollectionLogTable = @"
+CREATE TABLE IF NOT EXISTS collection_log (
+ log_id BIGINT PRIMARY KEY,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR,
+ collector_name VARCHAR NOT NULL,
+ collection_time TIMESTAMP NOT NULL,
+ duration_ms INTEGER,
+ status VARCHAR NOT NULL,
+ error_message VARCHAR,
+ rows_collected INTEGER,
+ sql_duration_ms INTEGER,
+ duckdb_duration_ms INTEGER
+)";
+
+ public const string CreateWaitStatsTable = @"
+CREATE TABLE IF NOT EXISTS wait_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ wait_type VARCHAR NOT NULL,
+ waiting_tasks_count BIGINT,
+ wait_time_ms BIGINT,
+ signal_wait_time_ms BIGINT,
+ delta_waiting_tasks BIGINT,
+ delta_wait_time_ms BIGINT,
+ delta_signal_wait_time_ms BIGINT
+)";
+
+ public const string CreateQueryStatsTable = @"
+CREATE TABLE IF NOT EXISTS query_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR,
+ query_hash VARCHAR,
+ query_plan_hash VARCHAR,
+ creation_time TIMESTAMP,
+ last_execution_time TIMESTAMP,
+ execution_count BIGINT,
+ total_worker_time BIGINT,
+ total_elapsed_time BIGINT,
+ total_logical_reads BIGINT,
+ total_logical_writes BIGINT,
+ total_physical_reads BIGINT,
+ total_clr_time BIGINT,
+ total_rows BIGINT,
+ total_spills BIGINT,
+ min_worker_time BIGINT,
+ max_worker_time BIGINT,
+ min_elapsed_time BIGINT,
+ max_elapsed_time BIGINT,
+ min_physical_reads BIGINT,
+ max_physical_reads BIGINT,
+ min_rows BIGINT,
+ max_rows BIGINT,
+ min_dop BIGINT,
+ max_dop BIGINT,
+ min_grant_kb BIGINT,
+ max_grant_kb BIGINT,
+ min_used_grant_kb BIGINT,
+ max_used_grant_kb BIGINT,
+ min_ideal_grant_kb BIGINT,
+ max_ideal_grant_kb BIGINT,
+ min_reserved_threads BIGINT,
+ max_reserved_threads BIGINT,
+ min_used_threads BIGINT,
+ max_used_threads BIGINT,
+ min_spills BIGINT,
+ max_spills BIGINT,
+ query_text VARCHAR,
+ query_plan_xml VARCHAR,
+ sql_handle VARCHAR,
+ plan_handle VARCHAR,
+ delta_execution_count BIGINT,
+ delta_worker_time BIGINT,
+ delta_elapsed_time BIGINT,
+ delta_logical_reads BIGINT,
+ delta_logical_writes BIGINT,
+ delta_physical_reads BIGINT,
+ delta_rows BIGINT,
+ delta_spills BIGINT
+)";
+
+ public const string CreateCpuUtilizationStatsTable = @"
+CREATE TABLE IF NOT EXISTS cpu_utilization_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ sample_time TIMESTAMP NOT NULL,
+ sqlserver_cpu_utilization INTEGER,
+ other_process_cpu_utilization INTEGER
+)";
+
+ public const string CreateFileIoStatsTable = @"
+CREATE TABLE IF NOT EXISTS file_io_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR,
+ file_name VARCHAR,
+ file_type VARCHAR,
+ physical_name VARCHAR,
+ size_mb DECIMAL(18,2),
+ num_of_reads BIGINT,
+ num_of_writes BIGINT,
+ read_bytes BIGINT,
+ write_bytes BIGINT,
+ io_stall_read_ms BIGINT,
+ io_stall_write_ms BIGINT,
+ io_stall_queued_read_ms BIGINT,
+ io_stall_queued_write_ms BIGINT,
+ delta_reads BIGINT,
+ delta_writes BIGINT,
+ delta_read_bytes BIGINT,
+ delta_write_bytes BIGINT,
+ delta_stall_read_ms BIGINT,
+ delta_stall_write_ms BIGINT,
+ delta_stall_queued_read_ms BIGINT,
+ delta_stall_queued_write_ms BIGINT
+)";
+
+ public const string CreateMemoryStatsTable = @"
+CREATE TABLE IF NOT EXISTS memory_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ total_physical_memory_mb DECIMAL(18,2),
+ available_physical_memory_mb DECIMAL(18,2),
+ total_page_file_mb DECIMAL(18,2),
+ available_page_file_mb DECIMAL(18,2),
+ system_memory_state VARCHAR,
+ sql_memory_model VARCHAR,
+ target_server_memory_mb DECIMAL(18,2),
+ total_server_memory_mb DECIMAL(18,2),
+ buffer_pool_mb DECIMAL(18,2),
+ plan_cache_mb DECIMAL(18,2),
+ max_workers_count INTEGER,
+ current_workers_count INTEGER
+)";
+
+ public const string CreateMemoryClerksTable = @"
+CREATE TABLE IF NOT EXISTS memory_clerks (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ clerk_type VARCHAR NOT NULL,
+ memory_mb DECIMAL(18,2)
+)";
+
+ public const string CreateDeadlocksTable = @"
+CREATE TABLE IF NOT EXISTS deadlocks (
+ deadlock_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ deadlock_time TIMESTAMP,
+ victim_process_id VARCHAR,
+ victim_sql_text VARCHAR,
+ deadlock_graph_xml VARCHAR
+)";
+
+ public const string CreateProcedureStatsTable = @"
+CREATE TABLE IF NOT EXISTS procedure_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR,
+ schema_name VARCHAR,
+ object_name VARCHAR,
+ object_type VARCHAR,
+ cached_time TIMESTAMP,
+ last_execution_time TIMESTAMP,
+ execution_count BIGINT,
+ total_worker_time BIGINT,
+ total_elapsed_time BIGINT,
+ total_logical_reads BIGINT,
+ total_physical_reads BIGINT,
+ total_logical_writes BIGINT,
+ min_worker_time BIGINT,
+ max_worker_time BIGINT,
+ min_elapsed_time BIGINT,
+ max_elapsed_time BIGINT,
+ min_logical_reads BIGINT,
+ max_logical_reads BIGINT,
+ min_physical_reads BIGINT,
+ max_physical_reads BIGINT,
+ min_logical_writes BIGINT,
+ max_logical_writes BIGINT,
+ total_spills BIGINT,
+ min_spills BIGINT,
+ max_spills BIGINT,
+ sql_handle VARCHAR,
+ plan_handle VARCHAR,
+ delta_execution_count BIGINT,
+ delta_worker_time BIGINT,
+ delta_elapsed_time BIGINT,
+ delta_logical_reads BIGINT,
+ delta_logical_writes BIGINT,
+ delta_physical_reads BIGINT
+)";
+
+ public const string CreateQueryStoreStatsTable = @"
+CREATE TABLE IF NOT EXISTS query_store_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR NOT NULL,
+ query_id BIGINT,
+ plan_id BIGINT,
+ execution_type_desc VARCHAR,
+ first_execution_time TIMESTAMP,
+ last_execution_time TIMESTAMP,
+ module_name VARCHAR,
+ query_text VARCHAR,
+ query_hash VARCHAR,
+ execution_count BIGINT,
+ avg_duration_us BIGINT,
+ min_duration_us BIGINT,
+ max_duration_us BIGINT,
+ avg_cpu_time_us BIGINT,
+ min_cpu_time_us BIGINT,
+ max_cpu_time_us BIGINT,
+ avg_logical_io_reads BIGINT,
+ min_logical_io_reads BIGINT,
+ max_logical_io_reads BIGINT,
+ avg_logical_io_writes BIGINT,
+ min_logical_io_writes BIGINT,
+ max_logical_io_writes BIGINT,
+ avg_physical_io_reads BIGINT,
+ min_physical_io_reads BIGINT,
+ max_physical_io_reads BIGINT,
+ avg_clr_time_us BIGINT,
+ min_clr_time_us BIGINT,
+ max_clr_time_us BIGINT,
+ min_dop BIGINT,
+ max_dop BIGINT,
+ avg_query_max_used_memory BIGINT,
+ min_query_max_used_memory BIGINT,
+ max_query_max_used_memory BIGINT,
+ avg_rowcount BIGINT,
+ min_rowcount BIGINT,
+ max_rowcount BIGINT,
+ avg_num_physical_io_reads BIGINT,
+ min_num_physical_io_reads BIGINT,
+ max_num_physical_io_reads BIGINT,
+ avg_log_bytes_used BIGINT,
+ min_log_bytes_used BIGINT,
+ max_log_bytes_used BIGINT,
+ avg_tempdb_space_used BIGINT,
+ min_tempdb_space_used BIGINT,
+ max_tempdb_space_used BIGINT,
+ plan_type VARCHAR,
+ plan_forcing_type VARCHAR,
+ is_forced_plan BOOLEAN,
+ force_failure_count BIGINT,
+ last_force_failure_reason VARCHAR,
+ compatibility_level INTEGER,
+ query_plan_text VARCHAR,
+ query_plan_hash VARCHAR
+)";
+
+ public const string CreateQuerySnapshotsTable = @"
+CREATE TABLE IF NOT EXISTS query_snapshots (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ session_id INTEGER,
+ database_name VARCHAR,
+ elapsed_time_formatted VARCHAR,
+ query_text VARCHAR,
+ query_plan VARCHAR,
+ live_query_plan VARCHAR,
+ status VARCHAR,
+ blocking_session_id INTEGER,
+ wait_type VARCHAR,
+ wait_time_ms BIGINT,
+ wait_resource VARCHAR,
+ cpu_time_ms BIGINT,
+ total_elapsed_time_ms BIGINT,
+ reads BIGINT,
+ writes BIGINT,
+ logical_reads BIGINT,
+ granted_query_memory_gb DECIMAL(18,2),
+ transaction_isolation_level VARCHAR,
+ dop INTEGER,
+ parallel_worker_count INTEGER,
+ login_name VARCHAR,
+ host_name VARCHAR,
+ program_name VARCHAR,
+ open_transaction_count INTEGER,
+ percent_complete DECIMAL(5,2)
+)";
+
+ public const string CreateTempdbStatsTable = @"
+CREATE TABLE IF NOT EXISTS tempdb_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ user_object_reserved_mb DECIMAL(18,2),
+ internal_object_reserved_mb DECIMAL(18,2),
+ version_store_reserved_mb DECIMAL(18,2),
+ total_reserved_mb DECIMAL(18,2),
+ unallocated_mb DECIMAL(18,2),
+ total_sessions_using_tempdb BIGINT,
+ top_session_id INTEGER,
+ top_session_tempdb_mb DECIMAL(18,2)
+)";
+
+ public const string CreatePerfmonStatsTable = @"
+CREATE TABLE IF NOT EXISTS perfmon_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ object_name VARCHAR NOT NULL,
+ counter_name VARCHAR NOT NULL,
+ instance_name VARCHAR,
+ cntr_value BIGINT,
+ delta_cntr_value BIGINT,
+ sample_interval_seconds INTEGER
+)";
+
+ public const string CreateServerConfigTable = @"
+CREATE TABLE IF NOT EXISTS server_config (
+ config_id BIGINT PRIMARY KEY,
+ capture_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ configuration_name VARCHAR NOT NULL,
+ value_configured BIGINT,
+ value_in_use BIGINT,
+ is_dynamic BOOLEAN,
+ is_advanced BOOLEAN
+)";
+
+ public const string CreateMemoryGrantStatsTable = @"
+CREATE TABLE IF NOT EXISTS memory_grant_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ resource_semaphore_id SMALLINT,
+ pool_id INTEGER,
+ target_memory_mb DECIMAL(18,2),
+ max_target_memory_mb DECIMAL(18,2),
+ total_memory_mb DECIMAL(18,2),
+ available_memory_mb DECIMAL(18,2),
+ granted_memory_mb DECIMAL(18,2),
+ used_memory_mb DECIMAL(18,2),
+ grantee_count INTEGER,
+ waiter_count INTEGER,
+ timeout_error_count BIGINT,
+ forced_grant_count BIGINT,
+ timeout_error_count_delta BIGINT,
+ forced_grant_count_delta BIGINT
+)";
+
+ public const string CreateWaitingTasksTable = @"
+CREATE TABLE IF NOT EXISTS waiting_tasks (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ session_id INTEGER,
+ wait_type VARCHAR,
+ wait_duration_ms BIGINT,
+ blocking_session_id INTEGER,
+ resource_description VARCHAR,
+ database_name VARCHAR
+)";
+
+ public const string CreateBlockedProcessReportsTable = @"
+CREATE TABLE IF NOT EXISTS blocked_process_reports (
+ blocked_report_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ event_time TIMESTAMP,
+ database_name VARCHAR,
+ blocked_spid INTEGER,
+ blocked_ecid INTEGER,
+ blocking_spid INTEGER,
+ blocking_ecid INTEGER,
+ wait_time_ms BIGINT,
+ wait_resource VARCHAR,
+ lock_mode VARCHAR,
+ blocked_status VARCHAR,
+ blocked_isolation_level VARCHAR,
+ blocked_log_used BIGINT,
+ blocked_transaction_count INTEGER,
+ blocked_client_app VARCHAR,
+ blocked_host_name VARCHAR,
+ blocked_login_name VARCHAR,
+ blocked_sql_text VARCHAR,
+ blocking_status VARCHAR,
+ blocking_isolation_level VARCHAR,
+ blocking_client_app VARCHAR,
+ blocking_host_name VARCHAR,
+ blocking_login_name VARCHAR,
+ blocking_sql_text VARCHAR,
+ blocked_transaction_name VARCHAR,
+ blocking_transaction_name VARCHAR,
+ blocked_last_tran_started TIMESTAMP,
+ blocking_last_tran_started TIMESTAMP,
+ blocked_last_batch_started TIMESTAMP,
+ blocking_last_batch_started TIMESTAMP,
+ blocked_last_batch_completed TIMESTAMP,
+ blocking_last_batch_completed TIMESTAMP,
+ blocked_priority INTEGER,
+ blocking_priority INTEGER,
+ blocked_process_report_xml VARCHAR
+)";
+
+ public const string CreateDatabaseConfigTable = @"
+CREATE TABLE IF NOT EXISTS database_config (
+ config_id BIGINT PRIMARY KEY,
+ capture_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR NOT NULL,
+ state_desc VARCHAR,
+ compatibility_level INTEGER,
+ collation_name VARCHAR,
+ recovery_model VARCHAR,
+ is_read_only BOOLEAN,
+ is_auto_close_on BOOLEAN,
+ is_auto_shrink_on BOOLEAN,
+ is_auto_create_stats_on BOOLEAN,
+ is_auto_update_stats_on BOOLEAN,
+ is_auto_update_stats_async_on BOOLEAN,
+ is_read_committed_snapshot_on BOOLEAN,
+ snapshot_isolation_state VARCHAR,
+ is_parameterization_forced BOOLEAN,
+ is_query_store_on BOOLEAN,
+ is_encrypted BOOLEAN,
+ is_trustworthy_on BOOLEAN,
+ is_db_chaining_on BOOLEAN,
+ is_broker_enabled BOOLEAN,
+ is_cdc_enabled BOOLEAN,
+ is_mixed_page_allocation_on BOOLEAN,
+ log_reuse_wait_desc VARCHAR,
+ page_verify_option VARCHAR,
+ target_recovery_time_seconds INTEGER,
+ delayed_durability VARCHAR,
+ is_accelerated_database_recovery_on BOOLEAN,
+ is_memory_optimized_enabled BOOLEAN,
+ is_optimized_locking_on BOOLEAN
+)";
+
+ // Index definitions
+ public const string CreateWaitStatsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_wait_stats_time ON wait_stats(server_id, collection_time)";
+
+ public const string CreateQueryStatsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_query_stats_time ON query_stats(server_id, collection_time)";
+
+ public const string CreateProcedureStatsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_procedure_stats_time ON procedure_stats(server_id, collection_time)";
+
+ public const string CreateQueryStoreIndex = @"
+CREATE INDEX IF NOT EXISTS idx_query_store_time ON query_store_stats(server_id, collection_time)";
+
+ public const string CreateQuerySnapshotsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_query_snapshots_time ON query_snapshots(server_id, collection_time)";
+
+ public const string CreateCpuIndex = @"
+CREATE INDEX IF NOT EXISTS idx_cpu_time ON cpu_utilization_stats(server_id, collection_time)";
+
+ public const string CreateFileIoIndex = @"
+CREATE INDEX IF NOT EXISTS idx_file_io_time ON file_io_stats(server_id, collection_time)";
+
+ public const string CreateMemoryIndex = @"
+CREATE INDEX IF NOT EXISTS idx_memory_time ON memory_stats(server_id, collection_time)";
+
+ public const string CreateTempdbIndex = @"
+CREATE INDEX IF NOT EXISTS idx_tempdb_time ON tempdb_stats(server_id, collection_time)";
+
+ public const string CreatePerfmonIndex = @"
+CREATE INDEX IF NOT EXISTS idx_perfmon_time ON perfmon_stats(server_id, collection_time)";
+
+ public const string CreateDeadlocksIndex = @"
+CREATE INDEX IF NOT EXISTS idx_deadlocks_time ON deadlocks(server_id, collection_time)";
+
+ public const string CreateCollectionLogIndex = @"
+CREATE INDEX IF NOT EXISTS idx_collection_log_time ON collection_log(server_id, collection_time)";
+
+ public const string CreateMemoryGrantStatsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_memory_grant_stats_time ON memory_grant_stats(server_id, collection_time)";
+
+ public const string CreateWaitingTasksIndex = @"
+CREATE INDEX IF NOT EXISTS idx_waiting_tasks_time ON waiting_tasks(server_id, collection_time)";
+
+ public const string CreateBlockedProcessReportsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_blocked_process_reports_time ON blocked_process_reports(server_id, collection_time)";
+
+ public const string CreateMemoryClerksIndex = @"
+CREATE INDEX IF NOT EXISTS idx_memory_clerks_time ON memory_clerks(server_id, collection_time)";
+
+ public const string CreateDatabaseScopedConfigTable = @"
+CREATE TABLE IF NOT EXISTS database_scoped_config (
+ config_id BIGINT PRIMARY KEY,
+ capture_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR NOT NULL,
+ configuration_name VARCHAR NOT NULL,
+ value VARCHAR,
+ value_for_secondary VARCHAR
+)";
+
+ public const string CreateDatabaseScopedConfigIndex = @"
+CREATE INDEX IF NOT EXISTS idx_database_scoped_config_time ON database_scoped_config(server_id, capture_time)";
+
+ public const string CreateTraceFlagsTable = @"
+CREATE TABLE IF NOT EXISTS trace_flags (
+ config_id BIGINT PRIMARY KEY,
+ capture_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ trace_flag INTEGER NOT NULL,
+ status BOOLEAN NOT NULL,
+ is_global BOOLEAN NOT NULL,
+ is_session BOOLEAN NOT NULL
+)";
+
+ public const string CreateTraceFlagsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_trace_flags_time ON trace_flags(server_id, capture_time)";
+
+ public const string CreateRunningJobsTable = @"
+CREATE TABLE IF NOT EXISTS running_jobs (
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ job_name VARCHAR NOT NULL,
+ job_id VARCHAR NOT NULL,
+ job_enabled BOOLEAN NOT NULL,
+ start_time TIMESTAMP NOT NULL,
+ current_duration_seconds BIGINT NOT NULL,
+ avg_duration_seconds BIGINT NOT NULL,
+ p95_duration_seconds BIGINT NOT NULL,
+ successful_run_count BIGINT NOT NULL,
+ is_running_long BOOLEAN NOT NULL,
+ percent_of_average DECIMAL(10,1)
+)";
+
+ public const string CreateRunningJobsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_running_jobs_time ON running_jobs(server_id, collection_time)";
+
+ public const string CreateDatabaseSizeStatsTable = @"
+CREATE TABLE IF NOT EXISTS database_size_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ database_name VARCHAR NOT NULL,
+ database_id INTEGER NOT NULL,
+ file_id INTEGER NOT NULL,
+ file_type_desc VARCHAR NOT NULL,
+ file_name VARCHAR NOT NULL,
+ physical_name VARCHAR NOT NULL,
+ total_size_mb DECIMAL(19,2) NOT NULL,
+ used_size_mb DECIMAL(19,2),
+ auto_growth_mb DECIMAL(19,2),
+ max_size_mb DECIMAL(19,2),
+ recovery_model_desc VARCHAR,
+ compatibility_level INTEGER,
+ state_desc VARCHAR,
+ volume_mount_point VARCHAR,
+ volume_total_mb DECIMAL(19,2),
+ volume_free_mb DECIMAL(19,2),
+ is_percent_growth BOOLEAN,
+ growth_pct INTEGER,
+ vlf_count INTEGER
+)";
+
+ public const string CreateDatabaseSizeStatsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_database_size_stats_time ON database_size_stats(server_id, collection_time)";
+
+ public const string CreateServerPropertiesTable = @"
+CREATE TABLE IF NOT EXISTS server_properties (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ edition VARCHAR NOT NULL,
+ product_version VARCHAR NOT NULL,
+ product_level VARCHAR NOT NULL,
+ product_update_level VARCHAR,
+ engine_edition INTEGER NOT NULL,
+ cpu_count INTEGER NOT NULL,
+ hyperthread_ratio INTEGER NOT NULL,
+ physical_memory_mb BIGINT NOT NULL,
+ socket_count INTEGER,
+ cores_per_socket INTEGER,
+ is_hadr_enabled BOOLEAN,
+ is_clustered BOOLEAN,
+ enterprise_features VARCHAR,
+ service_objective VARCHAR
+)";
+
+ public const string CreateServerPropertiesIndex = @"
+CREATE INDEX IF NOT EXISTS idx_server_properties_time ON server_properties(server_id, collection_time)";
+
+ public const string CreateSessionStatsTable = @"
+CREATE TABLE IF NOT EXISTS session_stats (
+ collection_id BIGINT PRIMARY KEY,
+ collection_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ program_name VARCHAR NOT NULL,
+ connection_count BIGINT NOT NULL,
+ running_count INTEGER NOT NULL,
+ sleeping_count INTEGER NOT NULL,
+ dormant_count INTEGER NOT NULL,
+ total_cpu_time_ms BIGINT,
+ total_reads BIGINT,
+ total_writes BIGINT,
+ total_logical_reads BIGINT
+)";
+
+ public const string CreateSessionStatsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_session_stats_time ON session_stats(server_id, collection_time)";
+
+ public const string CreateAlertLogTable = @"
+CREATE TABLE IF NOT EXISTS config_alert_log (
+ alert_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ server_name VARCHAR NOT NULL,
+ metric_name VARCHAR NOT NULL,
+ current_value DOUBLE NOT NULL,
+ threshold_value DOUBLE NOT NULL,
+ alert_sent BOOLEAN NOT NULL DEFAULT false,
+ notification_type VARCHAR NOT NULL DEFAULT 'tray',
+ send_error VARCHAR,
+ dismissed BOOLEAN NOT NULL DEFAULT false,
+ muted BOOLEAN NOT NULL DEFAULT false,
+ detail_text VARCHAR
+)";
+
+ public const string CreateMuteRulesTable = @"
+CREATE TABLE IF NOT EXISTS config_mute_rules (
+ id VARCHAR NOT NULL PRIMARY KEY,
+ enabled BOOLEAN NOT NULL DEFAULT true,
+ created_at_utc TIMESTAMP NOT NULL,
+ expires_at_utc TIMESTAMP,
+ reason VARCHAR,
+ server_name VARCHAR,
+ metric_name VARCHAR,
+ database_pattern VARCHAR,
+ query_text_pattern VARCHAR,
+ wait_type_pattern VARCHAR,
+ job_name_pattern VARCHAR
+)";
+
+ public const string CreateDismissedArchiveAlertsTable = @"
+CREATE TABLE IF NOT EXISTS dismissed_archive_alerts (
+ alert_time TIMESTAMP NOT NULL,
+ server_id INTEGER NOT NULL,
+ metric_name VARCHAR NOT NULL,
+ dismissed_at TIMESTAMP NOT NULL DEFAULT current_timestamp
+)";
+
+ public const string CreateDismissedArchiveAlertsIndex = @"
+CREATE INDEX IF NOT EXISTS idx_dismissed_archive_alerts
+ON dismissed_archive_alerts (alert_time, server_id, metric_name)";
+
+ ///
+ /// Returns all table creation statements in order.
+ ///
+ public static IEnumerable GetAllTableStatements()
+ {
+ yield return CreateServersTable;
+ yield return CreateCollectionScheduleTable;
+ yield return CreateCollectionLogTable;
+ yield return CreateWaitStatsTable;
+ yield return CreateQueryStatsTable;
+ yield return CreateCpuUtilizationStatsTable;
+ yield return CreateFileIoStatsTable;
+ yield return CreateMemoryStatsTable;
+ yield return CreateMemoryClerksTable;
+ yield return CreateDeadlocksTable;
+ yield return CreateProcedureStatsTable;
+ yield return CreateQueryStoreStatsTable;
+ yield return CreateQuerySnapshotsTable;
+ yield return CreateTempdbStatsTable;
+ yield return CreatePerfmonStatsTable;
+ yield return CreateServerConfigTable;
+ yield return CreateDatabaseConfigTable;
+ yield return CreateMemoryGrantStatsTable;
+ yield return CreateWaitingTasksTable;
+ yield return CreateBlockedProcessReportsTable;
+ yield return CreateDatabaseScopedConfigTable;
+ yield return CreateTraceFlagsTable;
+ yield return CreateRunningJobsTable;
+ yield return CreateDatabaseSizeStatsTable;
+ yield return CreateServerPropertiesTable;
+ yield return CreateSessionStatsTable;
+ yield return CreateAlertLogTable;
+ yield return CreateMuteRulesTable;
+ yield return CreateDismissedArchiveAlertsTable;
+ }
+
+ ///
+ /// Returns all index creation statements.
+ ///
+ public static IEnumerable GetAllIndexStatements()
+ {
+ yield return CreateWaitStatsIndex;
+ yield return CreateQueryStatsIndex;
+ yield return CreateProcedureStatsIndex;
+ yield return CreateQueryStoreIndex;
+ yield return CreateQuerySnapshotsIndex;
+ yield return CreateCpuIndex;
+ yield return CreateFileIoIndex;
+ yield return CreateMemoryIndex;
+ yield return CreateTempdbIndex;
+ yield return CreatePerfmonIndex;
+ yield return CreateDeadlocksIndex;
+ yield return CreateCollectionLogIndex;
+ yield return CreateMemoryGrantStatsIndex;
+ yield return CreateWaitingTasksIndex;
+ yield return CreateBlockedProcessReportsIndex;
+ yield return CreateMemoryClerksIndex;
+ yield return CreateDatabaseScopedConfigIndex;
+ yield return CreateTraceFlagsIndex;
+ yield return CreateRunningJobsIndex;
+ yield return CreateDatabaseSizeStatsIndex;
+ yield return CreateServerPropertiesIndex;
+ yield return CreateSessionStatsIndex;
+ yield return CreateDismissedArchiveAlertsIndex;
+ }
+}
diff --git a/Lite/Services/LocalDataService.AlertHistory.cs b/Lite/Services/LocalDataService.AlertHistory.cs
index 0e093bf..75d7deb 100644
--- a/Lite/Services/LocalDataService.AlertHistory.cs
+++ b/Lite/Services/LocalDataService.AlertHistory.cs
@@ -103,6 +103,7 @@ ORDER BY alert_time DESC
///
/// Dismisses specific alerts by marking them as dismissed in DuckDB.
/// Identifies rows by (alert_time, server_id, metric_name) composite key.
+ /// If an alert only exists in archived parquet, inserts into dismissed_archive_alerts instead.
///
public async Task DismissAlertsAsync(List alerts)
{
@@ -113,6 +114,7 @@ public async Task DismissAlertsAsync(List alerts)
using var connection = await OpenConnectionAsync();
int totalAffected = 0;
+ int archivedDismissed = 0;
foreach (var alert in alerts)
{
@@ -130,17 +132,37 @@ UPDATE config_alert_log
var affected = await command.ExecuteNonQueryAsync();
totalAffected += affected;
- if (affected == 0 && App.LogAlertDismissals)
- AppLogger.Warn("AlertDismiss", $"No rows updated for alert: time={alert.AlertTime:O}, server_id={alert.ServerId}, metric={alert.MetricName} — may be archived to parquet");
+ if (affected == 0)
+ {
+ using var sidecarCmd = connection.CreateCommand();
+ sidecarCmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+SELECT $1, $2, $3
+WHERE NOT EXISTS (
+ SELECT 1 FROM dismissed_archive_alerts
+ WHERE alert_time = $1
+ AND server_id = $2
+ AND metric_name = $3
+)";
+ sidecarCmd.Parameters.Add(new DuckDBParameter { Value = alert.AlertTime });
+ sidecarCmd.Parameters.Add(new DuckDBParameter { Value = alert.ServerId });
+ sidecarCmd.Parameters.Add(new DuckDBParameter { Value = alert.MetricName });
+ var sidecarAffected = await sidecarCmd.ExecuteNonQueryAsync();
+ archivedDismissed += sidecarAffected;
+
+ if (App.LogAlertDismissals)
+ AppLogger.Info("AlertDismiss", $"Archived alert dismissed via sidecar: time={alert.AlertTime:O}, server_id={alert.ServerId}, metric={alert.MetricName}");
+ }
}
if (App.LogAlertDismissals)
- AppLogger.Info("AlertDismiss", $"Dismiss complete: {totalAffected} row(s) updated out of {alerts.Count} selected");
- return totalAffected;
+ AppLogger.Info("AlertDismiss", $"Dismiss complete: {totalAffected} live + {archivedDismissed} archived out of {alerts.Count} selected");
+ return totalAffected + archivedDismissed;
}
///
/// Dismisses all visible (non-dismissed) alerts matching the current filter criteria.
+ /// Updates the live table, then inserts any remaining archived alerts into the sidecar table.
///
public async Task DismissAllVisibleAlertsAsync(int hoursBack, int? serverId = null)
{
@@ -173,10 +195,62 @@ UPDATE config_alert_log
command.Parameters.Add(new DuckDBParameter { Value = cutoff });
}
- var affected = await command.ExecuteNonQueryAsync();
+ var liveAffected = await command.ExecuteNonQueryAsync();
+
+ // Dismiss any remaining archived alerts that matched the filter
+ using var sidecarCmd = connection.CreateCommand();
+ if (serverId.HasValue)
+ {
+ sidecarCmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+SELECT v.alert_time, v.server_id, v.metric_name
+FROM v_config_alert_log v
+WHERE v.alert_time >= $1
+AND v.server_id = $2
+AND v.dismissed = FALSE
+AND NOT EXISTS (
+ SELECT 1 FROM config_alert_log l
+ WHERE l.alert_time = v.alert_time
+ AND l.server_id = v.server_id
+ AND l.metric_name = v.metric_name
+)
+AND NOT EXISTS (
+ SELECT 1 FROM dismissed_archive_alerts d
+ WHERE d.alert_time = v.alert_time
+ AND d.server_id = v.server_id
+ AND d.metric_name = v.metric_name
+)";
+ sidecarCmd.Parameters.Add(new DuckDBParameter { Value = cutoff });
+ sidecarCmd.Parameters.Add(new DuckDBParameter { Value = serverId.Value });
+ }
+ else
+ {
+ sidecarCmd.CommandText = @"
+INSERT INTO dismissed_archive_alerts (alert_time, server_id, metric_name)
+SELECT v.alert_time, v.server_id, v.metric_name
+FROM v_config_alert_log v
+WHERE v.alert_time >= $1
+AND v.dismissed = FALSE
+AND NOT EXISTS (
+ SELECT 1 FROM config_alert_log l
+ WHERE l.alert_time = v.alert_time
+ AND l.server_id = v.server_id
+ AND l.metric_name = v.metric_name
+)
+AND NOT EXISTS (
+ SELECT 1 FROM dismissed_archive_alerts d
+ WHERE d.alert_time = v.alert_time
+ AND d.server_id = v.server_id
+ AND d.metric_name = v.metric_name
+)";
+ sidecarCmd.Parameters.Add(new DuckDBParameter { Value = cutoff });
+ }
+
+ var archivedAffected = await sidecarCmd.ExecuteNonQueryAsync();
+
if (App.LogAlertDismissals)
- AppLogger.Info("AlertDismiss", $"Dismiss all complete: {affected} row(s) updated (cutoff={cutoff:O})");
- return affected;
+ AppLogger.Info("AlertDismiss", $"Dismiss all complete: {liveAffected} live + {archivedAffected} archived (cutoff={cutoff:O})");
+ return liveAffected + archivedAffected;
}
}