From 1ee07817b94f54d461a48da3d1602f079d93be00 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 11 Mar 2026 19:37:00 -0400 Subject: [PATCH] Fix ReadOnlyIntent connections sharing server_id in DuckDB When the same server was added twice with different ApplicationIntent (primary vs read-only replica), both produced identical server_id hashes because GetServerId() only hashed ServerName. All collected data merged under one ID, making charts identical for both tabs. Fix: GetServerNameForStorage() appends ":RO" to the server name when ReadOnlyIntent is true, producing a unique server_id and distinct server_name in DuckDB. Non-RO servers are unchanged (no migration). Also adds "(Read-Only)" indicators throughout the UI: sidebar, tab headers, status bar, alerts, tray notifications, overview cards, Manage Servers grid, and MCP tool responses. Status bar now updates on tab switch to reflect the active server. Tested with Azure SQL DB Business Critical readable replica. Co-Authored-By: Claude Opus 4.6 --- Lite/Controls/FinOpsTab.xaml.cs | 4 +-- Lite/Controls/ServerTab.xaml.cs | 9 ++++--- Lite/MainWindow.xaml | 4 +-- Lite/MainWindow.xaml.cs | 26 ++++++++++--------- Lite/Mcp/McpDiscoveryTools.cs | 9 ++++--- Lite/Mcp/ServerResolver.cs | 18 ++++++++----- Lite/Models/ServerConnection.cs | 14 ++++++++++ ...teCollectorService.BlockedProcessReport.cs | 2 +- Lite/Services/RemoteCollectorService.Cpu.cs | 2 +- .../RemoteCollectorService.DatabaseSize.cs | 2 +- .../RemoteCollectorService.Deadlocks.cs | 2 +- .../Services/RemoteCollectorService.FileIo.cs | 2 +- .../Services/RemoteCollectorService.Memory.cs | 4 +-- .../RemoteCollectorService.MemoryGrants.cs | 2 +- .../RemoteCollectorService.Perfmon.cs | 2 +- .../RemoteCollectorService.ProcedureStats.cs | 2 +- .../RemoteCollectorService.QuerySnapshots.cs | 2 +- .../RemoteCollectorService.QueryStats.cs | 2 +- .../RemoteCollectorService.QueryStore.cs | 2 +- .../RemoteCollectorService.RunningJobs.cs | 2 +- .../RemoteCollectorService.ServerConfig.cs | 8 +++--- ...RemoteCollectorService.ServerProperties.cs | 2 +- .../RemoteCollectorService.SessionStats.cs | 2 +- .../Services/RemoteCollectorService.TempDb.cs | 2 +- .../RemoteCollectorService.WaitStats.cs | 2 +- .../RemoteCollectorService.WaitingTasks.cs | 2 +- Lite/Services/RemoteCollectorService.cs | 12 ++++++++- Lite/Windows/ManageServersWindow.xaml | 4 +-- Lite/Windows/ManageServersWindow.xaml.cs | 2 +- 29 files changed, 91 insertions(+), 57 deletions(-) diff --git a/Lite/Controls/FinOpsTab.xaml.cs b/Lite/Controls/FinOpsTab.xaml.cs index 03e41fdc..d0f529fb 100644 --- a/Lite/Controls/FinOpsTab.xaml.cs +++ b/Lite/Controls/FinOpsTab.xaml.cs @@ -87,7 +87,7 @@ private void PopulateServerSelector() private int GetSelectedServerId() { if (ServerSelector.SelectedItem is ServerConnection server) - return RemoteCollectorService.GetDeterministicHashCode(server.ServerName); + return RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); return 0; } @@ -347,7 +347,7 @@ private async System.Threading.Tasks.Task LoadServerInventoryAsync(bool forceRef // Step 2: Get collected metrics from DuckDB try { - var serverId = RemoteCollectorService.GetDeterministicHashCode(server.ServerName); + var serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); var (avgCpu, storageGb, idleDbs, status) = await _dataService!.GetServerMetricsAsync(serverId); if (avgCpu.HasValue) item.AvgCpuPct = avgCpu; if (storageGb.HasValue) item.StorageTotalGb = storageGb; diff --git a/Lite/Controls/ServerTab.xaml.cs b/Lite/Controls/ServerTab.xaml.cs index 5e9a6148..b49c0766 100644 --- a/Lite/Controls/ServerTab.xaml.cs +++ b/Lite/Controls/ServerTab.xaml.cs @@ -36,6 +36,7 @@ public partial class ServerTab : UserControl private readonly LocalDataService _dataService; private readonly int _serverId; public int ServerId => _serverId; + public ServerConnection Server => _server; private readonly CredentialService _credentialService; private readonly DispatcherTimer _refreshTimer; private bool _isRefreshing; @@ -117,13 +118,13 @@ public ServerTab(ServerConnection server, DuckDbInitializer duckDb, CredentialSe _server = server; _dataService = new LocalDataService(duckDb); - _serverId = RemoteCollectorService.GetDeterministicHashCode(server.ServerName); + _serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); _credentialService = credentialService; UtcOffsetMinutes = utcOffsetMinutes; ServerTimeHelper.UtcOffsetMinutes = utcOffsetMinutes; - ServerNameText.Text = server.DisplayName; - ConnectionStatusText.Text = server.ServerName; + ServerNameText.Text = server.ReadOnlyIntent ? $"{server.DisplayName} (Read-Only)" : server.DisplayName; + ConnectionStatusText.Text = server.ServerNameDisplay; /* Apply default time range from settings */ TimeRangeCombo.SelectedIndex = App.DefaultTimeRangeHours switch @@ -582,7 +583,7 @@ private async System.Threading.Tasks.Task RefreshAllDataAsync(bool fullRefresh = } var tz = ServerTimeHelper.GetTimezoneLabel(ServerTimeHelper.CurrentDisplayMode); - ConnectionStatusText.Text = $"{_server.ServerName} - Last refresh: {DateTime.Now:HH:mm:ss} ({tz})"; + ConnectionStatusText.Text = $"{_server.ServerNameDisplay} - Last refresh: {DateTime.Now:HH:mm:ss} ({tz})"; } catch (Exception ex) { diff --git a/Lite/MainWindow.xaml b/Lite/MainWindow.xaml index b5aee93c..72b1b361 100644 --- a/Lite/MainWindow.xaml +++ b/Lite/MainWindow.xaml @@ -121,14 +121,14 @@ diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs index 328c020c..0b1881cc 100644 --- a/Lite/MainWindow.xaml.cs +++ b/Lite/MainWindow.xaml.cs @@ -227,6 +227,7 @@ private void ServerTabControl_SelectionChanged(object sender, SelectionChangedEv if (ServerTabControl.SelectedItem is TabItem { Content: ServerTab serverTab }) { ServerTimeHelper.UtcOffsetMinutes = serverTab.UtcOffsetMinutes; + StatusText.Text = $"Connected to {serverTab.Server.DisplayNameWithIntent}"; } /* Refresh alerts tab when selected */ @@ -416,8 +417,8 @@ private async Task RefreshOverviewAsync() { try { - var serverId = RemoteCollectorService.GetDeterministicHashCode(server.ServerName); - var summary = await _dataService.GetServerSummaryAsync(serverId, server.DisplayName); + var serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); + var summary = await _dataService.GetServerSummaryAsync(serverId, server.DisplayNameWithIntent); if (summary != null) { summary.ServerName = server.ServerName; @@ -563,23 +564,23 @@ private async void ConnectToServer(ServerConnection server) // Then collect fresh data and refresh again if (_collectorService != null) { - StatusText.Text = $"Collecting data from {server.DisplayName}..."; + StatusText.Text = $"Collecting data from {server.DisplayNameWithIntent}..."; try { await _collectorService.RunAllCollectorsForServerAsync(server); - StatusText.Text = $"Connected to {server.DisplayName} - Data loaded"; + StatusText.Text = $"Connected to {server.DisplayNameWithIntent} - Data loaded"; serverTab.RefreshData(); UpdateCollectorHealth(); _ = RefreshOverviewAsync(); } catch (Exception ex) { - StatusText.Text = $"Connected to {server.DisplayName} - Collection error: {ex.Message}"; + StatusText.Text = $"Connected to {server.DisplayNameWithIntent} - Collection error: {ex.Message}"; } } else { - StatusText.Text = $"Connected to {server.DisplayName}"; + StatusText.Text = $"Connected to {server.DisplayNameWithIntent}"; } } @@ -587,9 +588,10 @@ private StackPanel CreateTabHeader(ServerConnection server) { var panel = new StackPanel { Orientation = Orientation.Horizontal }; + var tabLabel = server.ReadOnlyIntent ? $"{server.DisplayName} (RO)" : server.DisplayName; panel.Children.Add(new TextBlock { - Text = server.DisplayName, + Text = tabLabel, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0) }); @@ -795,7 +797,7 @@ private void AddServerButton_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog() == true && dialog.AddedServer != null) { RefreshServerList(); - StatusText.Text = $"Added server: {dialog.AddedServer.DisplayName}"; + StatusText.Text = $"Added server: {dialog.AddedServer.DisplayNameWithIntent}"; } } @@ -880,7 +882,7 @@ private void ServerContextMenu_Remove_Click(object sender, RoutedEventArgs e) if (server == null) return; var result = MessageBox.Show( - $"Remove server '{server.DisplayName}'?", + $"Remove server '{server.DisplayNameWithIntent}'?", "Remove Server", MessageBoxButton.YesNo, MessageBoxImage.Question); @@ -890,7 +892,7 @@ private void ServerContextMenu_Remove_Click(object sender, RoutedEventArgs e) CloseServerTab(server.Id); _serverManager.DeleteServer(server.Id); RefreshServerList(); - StatusText.Text = $"Removed server: {server.DisplayName}"; + StatusText.Text = $"Removed server: {server.DisplayNameWithIntent}"; } } @@ -961,14 +963,14 @@ private void CheckConnectionsAndNotify() { _trayService?.ShowNotification( "Server Offline", - $"{server.DisplayName} is unreachable: {status.ErrorMessage ?? "unknown error"}", + $"{server.DisplayNameWithIntent} is unreachable: {status.ErrorMessage ?? "unknown error"}", Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Error); } else if (!wasOnline && isOnline) { _trayService?.ShowNotification( "Server Online", - $"{server.DisplayName} is back online", + $"{server.DisplayNameWithIntent} is back online", Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Info); } } diff --git a/Lite/Mcp/McpDiscoveryTools.cs b/Lite/Mcp/McpDiscoveryTools.cs index c9fc54b8..a11b815c 100644 --- a/Lite/Mcp/McpDiscoveryTools.cs +++ b/Lite/Mcp/McpDiscoveryTools.cs @@ -19,9 +19,10 @@ public static async Task ListServers(ServerManager serverManager, LocalD var lines = new List { $"Monitored servers ({servers.Count}):\n" }; foreach (var s in servers) { + var roTag = s.ReadOnlyIntent ? " [Read-Only]" : ""; var display = string.IsNullOrEmpty(s.DisplayName) || s.DisplayName == s.ServerName - ? s.ServerName - : $"{s.DisplayName} ({s.ServerName})"; + ? $"{s.ServerName}{roTag}" + : $"{s.DisplayName} ({s.ServerName}){roTag}"; var status = serverManager.GetConnectionStatus(s.Id); var statusText = status.IsOnline switch @@ -31,8 +32,8 @@ public static async Task ListServers(ServerManager serverManager, LocalD null => "Status not checked" }; - var serverId = RemoteCollectorService.GetDeterministicHashCode(s.ServerName); - var summary = await dataService.GetServerSummaryAsync(serverId, s.DisplayName ?? s.ServerName); + var serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(s)); + var summary = await dataService.GetServerSummaryAsync(serverId, s.DisplayNameWithIntent); var lastCollection = summary?.LastCollectionTime?.ToString("o") ?? "No data collected"; lines.Add($"- {display} [{statusText}] (last collection: {lastCollection})"); diff --git a/Lite/Mcp/ServerResolver.cs b/Lite/Mcp/ServerResolver.cs index ae66b719..8cdf9ec8 100644 --- a/Lite/Mcp/ServerResolver.cs +++ b/Lite/Mcp/ServerResolver.cs @@ -24,7 +24,8 @@ public static (int ServerId, string ServerName)? Resolve( if (servers.Count == 1) { var s = servers[0]; - return (RemoteCollectorService.GetDeterministicHashCode(s.ServerName), s.ServerName); + var storageName = RemoteCollectorService.GetServerNameForStorage(s); + return (RemoteCollectorService.GetDeterministicHashCode(storageName), storageName); } return null; @@ -37,7 +38,8 @@ public static (int ServerId, string ServerName)? Resolve( if (exact != null) { - return (RemoteCollectorService.GetDeterministicHashCode(exact.ServerName), exact.ServerName); + var exactName = RemoteCollectorService.GetServerNameForStorage(exact); + return (RemoteCollectorService.GetDeterministicHashCode(exactName), exactName); } /* Partial match */ @@ -47,7 +49,8 @@ public static (int ServerId, string ServerName)? Resolve( if (partial != null) { - return (RemoteCollectorService.GetDeterministicHashCode(partial.ServerName), partial.ServerName); + var partialName = RemoteCollectorService.GetServerNameForStorage(partial); + return (RemoteCollectorService.GetDeterministicHashCode(partialName), partialName); } return null; @@ -62,9 +65,12 @@ public static string ListAvailableServers(ServerManager serverManager) } var lines = servers.Select(s => - string.IsNullOrEmpty(s.DisplayName) || s.DisplayName == s.ServerName - ? s.ServerName - : $"{s.DisplayName} ({s.ServerName})"); + { + var roTag = s.ReadOnlyIntent ? " [Read-Only]" : ""; + return string.IsNullOrEmpty(s.DisplayName) || s.DisplayName == s.ServerName + ? $"{s.ServerName}{roTag}" + : $"{s.DisplayName} ({s.ServerName}){roTag}"; + }); return string.Join("\n", lines); } diff --git a/Lite/Models/ServerConnection.cs b/Lite/Models/ServerConnection.cs index a07c227f..24ca11c5 100644 --- a/Lite/Models/ServerConnection.cs +++ b/Lite/Models/ServerConnection.cs @@ -77,6 +77,20 @@ public bool UseWindowsAuth /// public bool ReadOnlyIntent { get; set; } = false; + /// + /// Server name with "(Read-Only)" suffix when ReadOnlyIntent is enabled. + /// Used for sidebar subtitle and status text. + /// + [JsonIgnore] + public string ServerNameDisplay => ReadOnlyIntent ? $"{ServerName} (Read-Only)" : ServerName; + + /// + /// Display name with "(Read-Only)" suffix when ReadOnlyIntent is enabled. + /// Used for alerts, tray notifications, status bar, and overview cards. + /// + [JsonIgnore] + public string DisplayNameWithIntent => ReadOnlyIntent ? $"{DisplayName} (Read-Only)" : DisplayName; + /// /// Display-only property for showing authentication type in UI. /// diff --git a/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs b/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs index b47a9284..cd5c90a7 100644 --- a/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs +++ b/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs @@ -418,7 +418,7 @@ as it lingers in the ring buffer across collection cycles. */ row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(parsed.EventTime) .AppendValue(parsed.DatabaseName) .AppendValue(parsed.BlockedSpid) diff --git a/Lite/Services/RemoteCollectorService.Cpu.cs b/Lite/Services/RemoteCollectorService.Cpu.cs index 800fb01c..a46c0681 100644 --- a/Lite/Services/RemoteCollectorService.Cpu.cs +++ b/Lite/Services/RemoteCollectorService.Cpu.cs @@ -147,7 +147,7 @@ drs.end_time DESC row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(sampleTime) .AppendValue(reader.IsDBNull(1) ? 0 : reader.GetInt32(1)) .AppendValue(reader.IsDBNull(2) ? 0 : reader.GetInt32(2)) diff --git a/Lite/Services/RemoteCollectorService.DatabaseSize.cs b/Lite/Services/RemoteCollectorService.DatabaseSize.cs index 96583975..7005819f 100644 --- a/Lite/Services/RemoteCollectorService.DatabaseSize.cs +++ b/Lite/Services/RemoteCollectorService.DatabaseSize.cs @@ -231,7 +231,7 @@ ORDER BY row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.DatabaseName) .AppendValue(r.DatabaseId) .AppendValue(r.FileId) diff --git a/Lite/Services/RemoteCollectorService.Deadlocks.cs b/Lite/Services/RemoteCollectorService.Deadlocks.cs index 883341b7..22c8e4df 100644 --- a/Lite/Services/RemoteCollectorService.Deadlocks.cs +++ b/Lite/Services/RemoteCollectorService.Deadlocks.cs @@ -401,7 +401,7 @@ and was previously misattributed as DuckDB time. */ row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(deadlockTime) .AppendValue(victimProcessId) .AppendValue(victimSqlText) diff --git a/Lite/Services/RemoteCollectorService.FileIo.cs b/Lite/Services/RemoteCollectorService.FileIo.cs index b9d280aa..5ce5540b 100644 --- a/Lite/Services/RemoteCollectorService.FileIo.cs +++ b/Lite/Services/RemoteCollectorService.FileIo.cs @@ -152,7 +152,7 @@ AND vfs.database_id < 32761 row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(stat.DatabaseName) .AppendValue(stat.FileName) .AppendValue(stat.FileType) diff --git a/Lite/Services/RemoteCollectorService.Memory.cs b/Lite/Services/RemoteCollectorService.Memory.cs index 55e36219..4af53786 100644 --- a/Lite/Services/RemoteCollectorService.Memory.cs +++ b/Lite/Services/RemoteCollectorService.Memory.cs @@ -175,7 +175,7 @@ FROM sys.dm_os_schedulers row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(totalPhysicalMb) .AppendValue(availablePhysicalMb) .AppendValue(totalPageFileMb) @@ -249,7 +249,7 @@ ORDER BY row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(reader.GetString(0)) .AppendValue(reader.GetDecimal(1)) .EndRow(); diff --git a/Lite/Services/RemoteCollectorService.MemoryGrants.cs b/Lite/Services/RemoteCollectorService.MemoryGrants.cs index a8db4a6e..244e08b2 100644 --- a/Lite/Services/RemoteCollectorService.MemoryGrants.cs +++ b/Lite/Services/RemoteCollectorService.MemoryGrants.cs @@ -99,7 +99,7 @@ WHERE deqrs.max_target_memory_kb IS NOT NULL row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.ResourceSemaphoreId) .AppendValue(r.PoolId) .AppendValue(r.TargetMb) diff --git a/Lite/Services/RemoteCollectorService.Perfmon.cs b/Lite/Services/RemoteCollectorService.Perfmon.cs index 9bfe972f..8e0b1ecb 100644 --- a/Lite/Services/RemoteCollectorService.Perfmon.cs +++ b/Lite/Services/RemoteCollectorService.Perfmon.cs @@ -186,7 +186,7 @@ WHERE pc.counter_name IN ( row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(objectName) .AppendValue(counterName) .AppendValue(instanceName) diff --git a/Lite/Services/RemoteCollectorService.ProcedureStats.cs b/Lite/Services/RemoteCollectorService.ProcedureStats.cs index 0b93c483..e46b71ab 100644 --- a/Lite/Services/RemoteCollectorService.ProcedureStats.cs +++ b/Lite/Services/RemoteCollectorService.ProcedureStats.cs @@ -290,7 +290,7 @@ ORDER BY s.total_elapsed_time DESC row.AppendValue(GenerateCollectionId()) /* collection_id */ .AppendValue(collectionTime) /* collection_time */ .AppendValue(serverId) /* server_id */ - .AppendValue(server.ServerName) /* server_name */ + .AppendValue(GetServerNameForStorage(server)) /* server_name */ .AppendValue(dbName) /* database_name */ .AppendValue(schemaName) /* schema_name */ .AppendValue(objectName) /* object_name */ diff --git a/Lite/Services/RemoteCollectorService.QuerySnapshots.cs b/Lite/Services/RemoteCollectorService.QuerySnapshots.cs index 93f5e363..da031295 100644 --- a/Lite/Services/RemoteCollectorService.QuerySnapshots.cs +++ b/Lite/Services/RemoteCollectorService.QuerySnapshots.cs @@ -137,7 +137,7 @@ private async Task CollectQuerySnapshotsAsync(ServerConnection server, Canc row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(Convert.ToInt32(reader.GetValue(0))) /* session_id */ .AppendValue(reader.IsDBNull(1) ? (string?)null : reader.GetString(1)) /* database_name */ .AppendValue(reader.IsDBNull(2) ? (string?)null : reader.GetString(2)) /* elapsed_time_formatted */ diff --git a/Lite/Services/RemoteCollectorService.QueryStats.cs b/Lite/Services/RemoteCollectorService.QueryStats.cs index 810e5ca6..cb0f535c 100644 --- a/Lite/Services/RemoteCollectorService.QueryStats.cs +++ b/Lite/Services/RemoteCollectorService.QueryStats.cs @@ -253,7 +253,7 @@ qs.total_elapsed_time DESC row.AppendValue(GenerateCollectionId()) /* collection_id */ .AppendValue(collectionTime) /* collection_time */ .AppendValue(serverId) /* server_id */ - .AppendValue(server.ServerName) /* server_name */ + .AppendValue(GetServerNameForStorage(server)) /* server_name */ .AppendValue(reader.IsDBNull(0) ? (string?)null : reader.GetString(0)) /* database_name */ .AppendValue(queryHash) /* query_hash */ .AppendValue(reader.IsDBNull(2) ? (string?)null : reader.GetString(2)) /* query_plan_hash */ diff --git a/Lite/Services/RemoteCollectorService.QueryStore.cs b/Lite/Services/RemoteCollectorService.QueryStore.cs index f8d916a6..612653c3 100644 --- a/Lite/Services/RemoteCollectorService.QueryStore.cs +++ b/Lite/Services/RemoteCollectorService.QueryStore.cs @@ -376,7 +376,7 @@ AND qst.query_sql_text NOT LIKE N''%PerformanceMonitorLite%'' row.AppendValue(GenerateCollectionId()) /* collection_id */ .AppendValue(collectionTime) /* collection_time */ .AppendValue(serverId) /* server_id */ - .AppendValue(server.ServerName) /* server_name */ + .AppendValue(GetServerNameForStorage(server)) /* server_name */ .AppendValue(dbName) /* database_name */ .AppendValue(reader.GetInt64(0)) /* query_id */ .AppendValue(reader.GetInt64(1)) /* plan_id */ diff --git a/Lite/Services/RemoteCollectorService.RunningJobs.cs b/Lite/Services/RemoteCollectorService.RunningJobs.cs index 00d26b87..9b56ca75 100644 --- a/Lite/Services/RemoteCollectorService.RunningJobs.cs +++ b/Lite/Services/RemoteCollectorService.RunningJobs.cs @@ -165,7 +165,7 @@ rj.current_duration_seconds DESC var row = appender.CreateRow(); row.AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.JobName) .AppendValue(r.JobId) .AppendValue(r.JobEnabled) diff --git a/Lite/Services/RemoteCollectorService.ServerConfig.cs b/Lite/Services/RemoteCollectorService.ServerConfig.cs index 6a7ba535..66f4b604 100644 --- a/Lite/Services/RemoteCollectorService.ServerConfig.cs +++ b/Lite/Services/RemoteCollectorService.ServerConfig.cs @@ -76,7 +76,7 @@ ORDER BY c.name row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.Name) .AppendValue(r.ValueConfigured) .AppendValue(r.ValueInUse) @@ -233,7 +233,7 @@ ORDER BY d.name row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.DbName) .AppendValue(r.StateDesc) .AppendValue(r.CompatLevel) @@ -443,7 +443,7 @@ ORDER BY dsc.name row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(dbName) .AppendValue(configName) .AppendValue(value) @@ -535,7 +535,7 @@ ORDER BY tf.trace_flag row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.TraceFlag) .AppendValue(r.Status) .AppendValue(r.IsGlobal) diff --git a/Lite/Services/RemoteCollectorService.ServerProperties.cs b/Lite/Services/RemoteCollectorService.ServerProperties.cs index 519f4deb..40bfd944 100644 --- a/Lite/Services/RemoteCollectorService.ServerProperties.cs +++ b/Lite/Services/RemoteCollectorService.ServerProperties.cs @@ -110,7 +110,7 @@ FROM sys.dm_os_sys_info AS osi row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(serverName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(edition) .AppendValue(productVersion) .AppendValue(productLevel) diff --git a/Lite/Services/RemoteCollectorService.SessionStats.cs b/Lite/Services/RemoteCollectorService.SessionStats.cs index a8281434..3eade6ec 100644 --- a/Lite/Services/RemoteCollectorService.SessionStats.cs +++ b/Lite/Services/RemoteCollectorService.SessionStats.cs @@ -120,7 +120,7 @@ ORDER BY row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(programName) .AppendValue(connectionCount) .AppendValue(runningCount) diff --git a/Lite/Services/RemoteCollectorService.TempDb.cs b/Lite/Services/RemoteCollectorService.TempDb.cs index 1f600121..d36c8da1 100644 --- a/Lite/Services/RemoteCollectorService.TempDb.cs +++ b/Lite/Services/RemoteCollectorService.TempDb.cs @@ -90,7 +90,7 @@ ORDER BY (ssu.user_objects_alloc_page_count + ssu.internal_objects_alloc_page_co row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(userObjMb) .AppendValue(internalObjMb) .AppendValue(versionStoreMb) diff --git a/Lite/Services/RemoteCollectorService.WaitStats.cs b/Lite/Services/RemoteCollectorService.WaitStats.cs index 0b5dcfbe..35ade9ab 100644 --- a/Lite/Services/RemoteCollectorService.WaitStats.cs +++ b/Lite/Services/RemoteCollectorService.WaitStats.cs @@ -135,7 +135,7 @@ WHERE ws.wait_time_ms > 0 row.AppendValue(GenerateCollectionId()) /* collection_id BIGINT */ .AppendValue(collectionTime) /* collection_time TIMESTAMP */ .AppendValue(serverId) /* server_id INTEGER */ - .AppendValue(server.ServerName) /* server_name VARCHAR */ + .AppendValue(GetServerNameForStorage(server)) /* server_name VARCHAR */ .AppendValue(stat.WaitType) /* wait_type VARCHAR */ .AppendValue(stat.WaitingTasks) /* waiting_tasks_count BIGINT */ .AppendValue(stat.WaitTimeMs) /* wait_time_ms BIGINT */ diff --git a/Lite/Services/RemoteCollectorService.WaitingTasks.cs b/Lite/Services/RemoteCollectorService.WaitingTasks.cs index d68fc1ee..bd9373b1 100644 --- a/Lite/Services/RemoteCollectorService.WaitingTasks.cs +++ b/Lite/Services/RemoteCollectorService.WaitingTasks.cs @@ -80,7 +80,7 @@ AND wt.wait_type IS NOT NULL row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue((int)sessionId) .AppendValue(waitType) .AppendValue(waitDurationMs) diff --git a/Lite/Services/RemoteCollectorService.cs b/Lite/Services/RemoteCollectorService.cs index 53fec1c8..3a10c56d 100644 --- a/Lite/Services/RemoteCollectorService.cs +++ b/Lite/Services/RemoteCollectorService.cs @@ -568,12 +568,22 @@ protected static long GenerateCollectionId() return Interlocked.Increment(ref s_idCounter); } + /// + /// Gets the server name used for DuckDB storage and hashing. + /// Appends ":RO" for ReadOnlyIntent connections so they get a + /// different server_id than read-write connections to the same host. + /// + internal static string GetServerNameForStorage(ServerConnection server) + { + return server.ReadOnlyIntent ? server.ServerName + ":RO" : server.ServerName; + } + /// /// Gets the numeric server ID from the server connection. /// protected static int GetServerId(ServerConnection server) { - return GetDeterministicHashCode(server.ServerName); + return GetDeterministicHashCode(GetServerNameForStorage(server)); } /// diff --git a/Lite/Windows/ManageServersWindow.xaml b/Lite/Windows/ManageServersWindow.xaml index 0060bd7f..63ad4208 100644 --- a/Lite/Windows/ManageServersWindow.xaml +++ b/Lite/Windows/ManageServersWindow.xaml @@ -47,8 +47,8 @@ MouseDoubleClick="ServersGrid_MouseDoubleClick" ContextMenu="{StaticResource DataGridContextMenu}"> - - + + diff --git a/Lite/Windows/ManageServersWindow.xaml.cs b/Lite/Windows/ManageServersWindow.xaml.cs index 8ef3447c..03b6b8e8 100644 --- a/Lite/Windows/ManageServersWindow.xaml.cs +++ b/Lite/Windows/ManageServersWindow.xaml.cs @@ -78,7 +78,7 @@ private void DeleteButton_Click(object sender, RoutedEventArgs e) } var result = MessageBox.Show( - $"Delete server '{selected.DisplayName}'?\n\nThis will remove the server and its stored credentials.", + $"Delete server '{selected.DisplayNameWithIntent}'?\n\nThis will remove the server and its stored credentials.", "Delete Server", MessageBoxButton.YesNo, MessageBoxImage.Warning);