diff --git a/Lite/Controls/ServerTab.xaml b/Lite/Controls/ServerTab.xaml
index c99a7afe..342dd44b 100644
--- a/Lite/Controls/ServerTab.xaml
+++ b/Lite/Controls/ServerTab.xaml
@@ -911,21 +911,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -935,6 +983,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/Lite/Database/DuckDbInitializer.cs b/Lite/Database/DuckDbInitializer.cs
index dfaa4f75..562a7c13 100644
--- a/Lite/Database/DuckDbInitializer.cs
+++ b/Lite/Database/DuckDbInitializer.cs
@@ -19,7 +19,7 @@ public class DuckDbInitializer
///
/// Current schema version. Increment this when schema changes require table rebuilds.
///
- internal const int CurrentSchemaVersion = 10;
+ internal const int CurrentSchemaVersion = 11;
private readonly string _archivePath;
@@ -359,6 +359,15 @@ can be identified by server without needing a lookup table. */
/* Table doesn't exist yet — will be created with correct schema below */
}
}
+
+ if (fromVersion < 11)
+ {
+ /* v11: Expanded database_config from 9 to 28 columns (sys.databases).
+ Added state_desc, collation, RCSI, snapshot isolation, stats settings,
+ encryption, security, and version-gated columns (ADR, memory optimized, optimized locking). */
+ _logger?.LogInformation("Running migration to v11: rebuilding database_config for expanded sys.databases columns");
+ await ExecuteNonQueryAsync(connection, "DROP TABLE IF EXISTS database_config");
+ }
}
///
diff --git a/Lite/Database/Schema.cs b/Lite/Database/Schema.cs
index 24f46ab5..7b3961f5 100644
--- a/Lite/Database/Schema.cs
+++ b/Lite/Database/Schema.cs
@@ -385,14 +385,33 @@ CREATE TABLE IF NOT EXISTS database_config (
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
+ delayed_durability VARCHAR,
+ is_accelerated_database_recovery_on BOOLEAN,
+ is_memory_optimized_enabled BOOLEAN,
+ is_optimized_locking_on BOOLEAN
)";
// Index definitions
diff --git a/Lite/Services/LocalDataService.Config.cs b/Lite/Services/LocalDataService.Config.cs
index f3983ba7..82b9e9ee 100644
--- a/Lite/Services/LocalDataService.Config.cs
+++ b/Lite/Services/LocalDataService.Config.cs
@@ -56,9 +56,14 @@ public async Task> GetLatestDatabaseConfigAsync(int serv
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();
command.CommandText = @"
-SELECT database_name, compatibility_level, recovery_model,
- is_auto_close_on, is_auto_shrink_on, is_query_store_on,
- page_verify_option, target_recovery_time_seconds, delayed_durability
+SELECT database_name, state_desc, compatibility_level, collation_name, recovery_model,
+ is_read_only, is_auto_close_on, is_auto_shrink_on,
+ is_auto_create_stats_on, is_auto_update_stats_on, is_auto_update_stats_async_on,
+ is_read_committed_snapshot_on, snapshot_isolation_state, is_parameterization_forced,
+ is_query_store_on, is_encrypted, is_trustworthy_on, is_db_chaining_on,
+ is_broker_enabled, is_cdc_enabled, is_mixed_page_allocation_on,
+ log_reuse_wait_desc, page_verify_option, target_recovery_time_seconds, delayed_durability,
+ is_accelerated_database_recovery_on, is_memory_optimized_enabled, is_optimized_locking_on
FROM database_config
WHERE server_id = $1
AND capture_time = (SELECT MAX(capture_time) FROM database_config WHERE server_id = $1)
@@ -70,17 +75,37 @@ FROM database_config
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
+ var ordinal = 0;
items.Add(new DatabaseConfigRow
{
- DatabaseName = reader.GetString(0),
- CompatibilityLevel = reader.IsDBNull(1) ? 0 : reader.GetInt32(1),
- RecoveryModel = reader.IsDBNull(2) ? "" : reader.GetString(2),
- IsAutoCloseOn = !reader.IsDBNull(3) && reader.GetBoolean(3),
- IsAutoShrinkOn = !reader.IsDBNull(4) && reader.GetBoolean(4),
- IsQueryStoreOn = !reader.IsDBNull(5) && reader.GetBoolean(5),
- PageVerifyOption = reader.IsDBNull(6) ? "" : reader.GetString(6),
- TargetRecoveryTimeSeconds = reader.IsDBNull(7) ? 0 : reader.GetInt32(7),
- DelayedDurability = reader.IsDBNull(8) ? "" : reader.GetString(8)
+ DatabaseName = reader.GetString(ordinal++),
+ StateDesc = reader.IsDBNull(ordinal) ? "" : reader.GetString(ordinal),
+ CompatibilityLevel = reader.IsDBNull(++ordinal) ? 0 : reader.GetInt32(ordinal),
+ CollationName = reader.IsDBNull(++ordinal) ? "" : reader.GetString(ordinal),
+ RecoveryModel = reader.IsDBNull(++ordinal) ? "" : reader.GetString(ordinal),
+ IsReadOnly = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsAutoCloseOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsAutoShrinkOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsAutoCreateStatsOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsAutoUpdateStatsOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsAutoUpdateStatsAsyncOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsRcsiOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ SnapshotIsolationState = reader.IsDBNull(++ordinal) ? "" : reader.GetString(ordinal),
+ IsParameterizationForced = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsQueryStoreOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsEncrypted = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsTrustworthyOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsDbChainingOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsBrokerEnabled = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsCdcEnabled = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsMixedPageAllocationOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ LogReuseWaitDesc = reader.IsDBNull(++ordinal) ? "" : reader.GetString(ordinal),
+ PageVerifyOption = reader.IsDBNull(++ordinal) ? "" : reader.GetString(ordinal),
+ TargetRecoveryTimeSeconds = reader.IsDBNull(++ordinal) ? 0 : reader.GetInt32(ordinal),
+ DelayedDurability = reader.IsDBNull(++ordinal) ? "" : reader.GetString(ordinal),
+ IsAcceleratedDatabaseRecoveryOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsMemoryOptimizedEnabled = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
+ IsOptimizedLockingOn = !reader.IsDBNull(++ordinal) && reader.GetBoolean(ordinal),
});
}
@@ -167,17 +192,53 @@ public class ServerConfigRow
public class DatabaseConfigRow
{
public string DatabaseName { get; set; } = "";
+ public string StateDesc { get; set; } = "";
public int CompatibilityLevel { get; set; }
+ public string CollationName { get; set; } = "";
public string RecoveryModel { get; set; } = "";
+ public bool IsReadOnly { get; set; }
public bool IsAutoCloseOn { get; set; }
public bool IsAutoShrinkOn { get; set; }
+ public bool IsAutoCreateStatsOn { get; set; }
+ public bool IsAutoUpdateStatsOn { get; set; }
+ public bool IsAutoUpdateStatsAsyncOn { get; set; }
+ public bool IsRcsiOn { get; set; }
+ public string SnapshotIsolationState { get; set; } = "";
+ public bool IsParameterizationForced { get; set; }
public bool IsQueryStoreOn { get; set; }
+ public bool IsEncrypted { get; set; }
+ public bool IsTrustworthyOn { get; set; }
+ public bool IsDbChainingOn { get; set; }
+ public bool IsBrokerEnabled { get; set; }
+ public bool IsCdcEnabled { get; set; }
+ public bool IsMixedPageAllocationOn { get; set; }
+ public string LogReuseWaitDesc { get; set; } = "";
public string PageVerifyOption { get; set; } = "";
public int TargetRecoveryTimeSeconds { get; set; }
public string DelayedDurability { get; set; } = "";
+ public bool IsAcceleratedDatabaseRecoveryOn { get; set; }
+ public bool IsMemoryOptimizedEnabled { get; set; }
+ public bool IsOptimizedLockingOn { get; set; }
+
+ /* Display properties for DataGrid (bool → Yes/No) */
+ public string ReadOnlyDisplay => IsReadOnly ? "Yes" : "No";
public string AutoCloseDisplay => IsAutoCloseOn ? "Yes" : "No";
public string AutoShrinkDisplay => IsAutoShrinkOn ? "Yes" : "No";
+ public string AutoCreateStatsDisplay => IsAutoCreateStatsOn ? "Yes" : "No";
+ public string AutoUpdateStatsDisplay => IsAutoUpdateStatsOn ? "Yes" : "No";
+ public string AutoUpdateStatsAsyncDisplay => IsAutoUpdateStatsAsyncOn ? "Yes" : "No";
+ public string RcsiDisplay => IsRcsiOn ? "Yes" : "No";
+ public string ParameterizationForcedDisplay => IsParameterizationForced ? "Yes" : "No";
public string QueryStoreDisplay => IsQueryStoreOn ? "Yes" : "No";
+ public string EncryptedDisplay => IsEncrypted ? "Yes" : "No";
+ public string TrustworthyDisplay => IsTrustworthyOn ? "Yes" : "No";
+ public string DbChainingDisplay => IsDbChainingOn ? "Yes" : "No";
+ public string BrokerEnabledDisplay => IsBrokerEnabled ? "Yes" : "No";
+ public string CdcEnabledDisplay => IsCdcEnabled ? "Yes" : "No";
+ public string MixedPageAllocationDisplay => IsMixedPageAllocationOn ? "Yes" : "No";
+ public string AdrDisplay => IsAcceleratedDatabaseRecoveryOn ? "Yes" : "No";
+ public string MemoryOptimizedDisplay => IsMemoryOptimizedEnabled ? "Yes" : "No";
+ public string OptimizedLockingDisplay => IsOptimizedLockingOn ? "Yes" : "No";
}
public class DatabaseScopedConfigRow
diff --git a/Lite/Services/RemoteCollectorService.ServerConfig.cs b/Lite/Services/RemoteCollectorService.ServerConfig.cs
index bf19492d..1e6d6e70 100644
--- a/Lite/Services/RemoteCollectorService.ServerConfig.cs
+++ b/Lite/Services/RemoteCollectorService.ServerConfig.cs
@@ -96,22 +96,64 @@ ORDER BY c.name
///
/// Collects database configuration from sys.databases. On-load only, not scheduled.
+ /// Version-gated columns are conditionally included based on the server's major version.
///
private async Task CollectDatabaseConfigAsync(ServerConnection server, CancellationToken cancellationToken)
{
- const string query = @"
-SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+ var serverStatus = _serverManager.GetConnectionStatus(server.Id);
+ var majorVersion = serverStatus.SqlMajorVersion;
-SELECT
+ /* Base columns available on all supported versions (2016+) */
+ var selectColumns = @"
database_name = d.name,
+ state_desc = d.state_desc,
compatibility_level = d.compatibility_level,
+ collation_name = d.collation_name,
recovery_model = d.recovery_model_desc,
+ is_read_only = d.is_read_only,
is_auto_close_on = d.is_auto_close_on,
is_auto_shrink_on = d.is_auto_shrink_on,
+ is_auto_create_stats_on = d.is_auto_create_stats_on,
+ is_auto_update_stats_on = d.is_auto_update_stats_on,
+ is_auto_update_stats_async_on = d.is_auto_update_stats_async_on,
+ is_read_committed_snapshot_on = d.is_read_committed_snapshot_on,
+ snapshot_isolation_state = d.snapshot_isolation_state_desc,
+ is_parameterization_forced = d.is_parameterization_forced,
is_query_store_on = d.is_query_store_on,
+ is_encrypted = d.is_encrypted,
+ is_trustworthy_on = d.is_trustworthy_on,
+ is_db_chaining_on = d.is_db_chaining_on,
+ is_broker_enabled = d.is_broker_enabled,
+ is_cdc_enabled = d.is_cdc_enabled,
+ is_mixed_page_allocation_on = d.is_mixed_page_allocation_on,
+ log_reuse_wait_desc = d.log_reuse_wait_desc,
page_verify_option = d.page_verify_option_desc,
target_recovery_time_seconds = d.target_recovery_time_in_seconds,
- delayed_durability = d.delayed_durability_desc
+ delayed_durability = d.delayed_durability_desc";
+
+ /* SQL Server 2019+ (major version 15), or Azure SQL DB/MI which always have these */
+ var isAzure = serverStatus.SqlEngineEdition == 5 || serverStatus.SqlEngineEdition == 8;
+ var has2019Columns = majorVersion >= 15 || majorVersion == 0 || isAzure;
+ if (has2019Columns)
+ {
+ selectColumns += @",
+ is_accelerated_database_recovery_on = d.is_accelerated_database_recovery_on,
+ is_memory_optimized_enabled = d.is_memory_optimized_enabled";
+ }
+
+ /* SQL Server 2025+ (major version 17) */
+ var has2025Columns = majorVersion >= 17;
+ if (has2025Columns)
+ {
+ selectColumns += @",
+ is_optimized_locking_on = d.is_optimized_locking_on";
+ }
+
+ var query = $@"
+SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+
+SELECT
+{selectColumns}
FROM sys.databases AS d
WHERE (d.database_id > 4 OR d.database_id = 2)
AND d.database_id < 32761
@@ -125,7 +167,7 @@ ORDER BY d.name
_lastSqlMs = 0;
_lastDuckDbMs = 0;
- var rows = new List<(string DbName, int CompatLevel, string? RecoveryModel, bool AutoClose, bool AutoShrink, bool QueryStore, string? PageVerify, int TargetRecovery, string? DelayedDurability)>();
+ var rows = new List();
var sqlSw = Stopwatch.StartNew();
using var sqlConnection = await CreateConnectionAsync(server, cancellationToken);
@@ -135,16 +177,48 @@ ORDER BY d.name
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
- rows.Add((
- reader.GetString(0),
- Convert.ToInt32(reader.GetValue(1)),
- reader.IsDBNull(2) ? null : reader.GetString(2),
- reader.GetBoolean(3),
- reader.GetBoolean(4),
- reader.GetBoolean(5),
- reader.IsDBNull(6) ? null : reader.GetString(6),
- Convert.ToInt32(reader.GetValue(7)),
- reader.IsDBNull(8) ? null : reader.GetString(8)));
+ var ordinal = 0;
+ var r = new DatabaseConfigCollected
+ {
+ DbName = reader.GetString(ordinal++),
+ StateDesc = reader.IsDBNull(ordinal) ? null : reader.GetString(ordinal),
+ CompatLevel = Convert.ToInt32(reader.GetValue(++ordinal)),
+ CollationName = reader.IsDBNull(++ordinal) ? null : reader.GetString(ordinal),
+ RecoveryModel = reader.IsDBNull(++ordinal) ? null : reader.GetString(ordinal),
+ IsReadOnly = reader.GetBoolean(++ordinal),
+ AutoClose = reader.GetBoolean(++ordinal),
+ AutoShrink = reader.GetBoolean(++ordinal),
+ AutoCreateStats = reader.GetBoolean(++ordinal),
+ AutoUpdateStats = reader.GetBoolean(++ordinal),
+ AutoUpdateStatsAsync = reader.GetBoolean(++ordinal),
+ Rcsi = reader.GetBoolean(++ordinal),
+ SnapshotIsolation = reader.IsDBNull(++ordinal) ? null : reader.GetString(ordinal),
+ ParameterizationForced = reader.GetBoolean(++ordinal),
+ QueryStore = reader.GetBoolean(++ordinal),
+ Encrypted = reader.GetBoolean(++ordinal),
+ Trustworthy = reader.GetBoolean(++ordinal),
+ DbChaining = reader.GetBoolean(++ordinal),
+ BrokerEnabled = reader.GetBoolean(++ordinal),
+ CdcEnabled = reader.GetBoolean(++ordinal),
+ MixedPageAllocation = reader.GetBoolean(++ordinal),
+ LogReuseWait = reader.IsDBNull(++ordinal) ? null : reader.GetString(ordinal),
+ PageVerify = reader.IsDBNull(++ordinal) ? null : reader.GetString(ordinal),
+ TargetRecovery = Convert.ToInt32(reader.GetValue(++ordinal)),
+ DelayedDurability = reader.IsDBNull(++ordinal) ? null : reader.GetString(ordinal),
+ };
+
+ if (has2019Columns)
+ {
+ r.AcceleratedDatabaseRecovery = reader.GetBoolean(++ordinal);
+ r.MemoryOptimized = reader.GetBoolean(++ordinal);
+ }
+
+ if (has2025Columns)
+ {
+ r.OptimizedLocking = reader.GetBoolean(++ordinal);
+ }
+
+ rows.Add(r);
}
sqlSw.Stop();
@@ -161,15 +235,48 @@ ORDER BY d.name
.AppendValue(serverId)
.AppendValue(server.ServerName)
.AppendValue(r.DbName)
+ .AppendValue(r.StateDesc)
.AppendValue(r.CompatLevel)
+ .AppendValue(r.CollationName)
.AppendValue(r.RecoveryModel)
+ .AppendValue(r.IsReadOnly)
.AppendValue(r.AutoClose)
.AppendValue(r.AutoShrink)
+ .AppendValue(r.AutoCreateStats)
+ .AppendValue(r.AutoUpdateStats)
+ .AppendValue(r.AutoUpdateStatsAsync)
+ .AppendValue(r.Rcsi)
+ .AppendValue(r.SnapshotIsolation)
+ .AppendValue(r.ParameterizationForced)
.AppendValue(r.QueryStore)
+ .AppendValue(r.Encrypted)
+ .AppendValue(r.Trustworthy)
+ .AppendValue(r.DbChaining)
+ .AppendValue(r.BrokerEnabled)
+ .AppendValue(r.CdcEnabled)
+ .AppendValue(r.MixedPageAllocation)
+ .AppendValue(r.LogReuseWait)
.AppendValue(r.PageVerify)
.AppendValue(r.TargetRecovery)
- .AppendValue(r.DelayedDurability)
- .EndRow();
+ .AppendValue(r.DelayedDurability);
+
+ /* Version-gated columns: write value if collected, NULL otherwise */
+ if (r.AcceleratedDatabaseRecovery.HasValue)
+ row.AppendValue(r.AcceleratedDatabaseRecovery.Value);
+ else
+ row.AppendNullValue();
+
+ if (r.MemoryOptimized.HasValue)
+ row.AppendValue(r.MemoryOptimized.Value);
+ else
+ row.AppendNullValue();
+
+ if (r.OptimizedLocking.HasValue)
+ row.AppendValue(r.OptimizedLocking.Value);
+ else
+ row.AppendNullValue();
+
+ row.EndRow();
rowsCollected++;
}
@@ -181,6 +288,38 @@ ORDER BY d.name
return rowsCollected;
}
+ private class DatabaseConfigCollected
+ {
+ public string DbName { get; set; } = "";
+ public string? StateDesc { get; set; }
+ public int CompatLevel { get; set; }
+ public string? CollationName { get; set; }
+ public string? RecoveryModel { get; set; }
+ public bool IsReadOnly { get; set; }
+ public bool AutoClose { get; set; }
+ public bool AutoShrink { get; set; }
+ public bool AutoCreateStats { get; set; }
+ public bool AutoUpdateStats { get; set; }
+ public bool AutoUpdateStatsAsync { get; set; }
+ public bool Rcsi { get; set; }
+ public string? SnapshotIsolation { get; set; }
+ public bool ParameterizationForced { get; set; }
+ public bool QueryStore { get; set; }
+ public bool Encrypted { get; set; }
+ public bool Trustworthy { get; set; }
+ public bool DbChaining { get; set; }
+ public bool BrokerEnabled { get; set; }
+ public bool CdcEnabled { get; set; }
+ public bool MixedPageAllocation { get; set; }
+ public string? LogReuseWait { get; set; }
+ public string? PageVerify { get; set; }
+ public int TargetRecovery { get; set; }
+ public string? DelayedDurability { get; set; }
+ public bool? AcceleratedDatabaseRecovery { get; set; }
+ public bool? MemoryOptimized { get; set; }
+ public bool? OptimizedLocking { get; set; }
+ }
+
///
/// Collects database-scoped configurations from sys.database_scoped_configurations
/// for each online user database. On-load only, not scheduled.