Skip to content

Release v2.9.0#911

Merged
erikdarlingdata merged 82 commits intomainfrom
dev
Apr 29, 2026
Merged

Release v2.9.0#911
erikdarlingdata merged 82 commits intomainfrom
dev

Conversation

@erikdarlingdata
Copy link
Copy Markdown
Owner

Summary

Release v2.9.0.

CHANGELOG also fixes the v2.8.0 entry's date (was TBD — actual ship date 2026-04-22) and moves the Off preset (#888) out of 2.8.0 into 2.9.0 since #891 landed after the v2.8.0 tag.

See CHANGELOG.md for the full list.

Test plan

  • Installer.Tests — 46/46 passed
  • dotnet build -c Debug — 0 errors
  • Fresh install on sql2016 (--reinstall) — 54/54 scripts, 45 collectors, 0 errors
  • Upgrade install on sql2017 (2.8.0 → 2.9.0) — 26 tables preserved, all row counts grew, exclusions table created
  • Upgrade install on sql2022 (2.8.0 → 2.9.0) + idempotency re-run — 1.5M+ row tables preserved
  • Multi-hop on sql2016 via CLI — v2.6.0 → 2.9.0, v2.7.0 → 2.9.0, v2.8.0 → 2.9.0, all clean
  • Dashboard GUI upgrade on sql2019 + sql2025 (embedded-resource provider — [BUG] Invalid Column Name errors for collect.database_size_stats_collector #772 regression surface)
  • Uninstall path on sql2016 — DB / Agent jobs / XE sessions removed
  • Cloud platform testing — Azure SQL DB + AWS RDS provisioned, connected to Lite, FinOps click-through
  • 1h41m runtime burn-in across both apps — 0 errors in app logs, 0 errors in config.collection_log across all 5 servers (sql2016/2017/2019/2022/2025)
  • Lite Parquet archive event captured cleanly at T+1h
  • installation_history SUCCESS with correct previous_version across all upgrade paths
  • Today's nightly build (2.9.0-nightly.20260429) green

🤖 Generated with Claude Code

ClaudioESSilva and others added 30 commits April 15, 2026 17:20
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add trailing newlines to ScrollPanBehavior files
…ving race

Addresses security findings from #840:
- #846: Escape single quotes in file paths interpolated into read_parquet() and COPY TO
- #847: Use DuckDB $1 parameters for DateTime values instead of string interpolation
- #849: Make IsArchiving volatile-backed to prevent stale reads across threads

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ening

Harden DuckDB queries and fix IsArchiving race condition
Moves Teams and Slack webhook URLs from plaintext settings.json/preferences.json
to Windows Credential Manager (DPAPI-encrypted), matching the existing pattern
used for SMTP passwords and SQL Server credentials.

Includes automatic migration: on first settings load, any plaintext URLs are
moved to Credential Manager and removed from the JSON file.

Closes #848

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Encrypt webhook URLs with DPAPI via Credential Manager
…rst visit

Initial tab open and Refresh button now only load the currently visible tab.
First switch to any tab triggers a full refresh of that tab (all sub-tabs).
Subsequent refreshes only hit the active sub-tab.

Ctrl+Click on Refresh Tab (or Ctrl+F5) refreshes all tabs at once.
Apply to All Tabs retains existing full-refresh behavior.

Fixes #835 — prevents heavy queries (e.g. GetQueryStatsAsync) from running
on tab open when the user is only viewing Overview.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
GetQueryStatsAsync, GetProcedureStatsAsync, and GetQueryStoreDataAsync
were returning unbounded result sets. With 49 databases and 742K rows
in query_stats over 3 days, the GROUP BY with plan XML could produce
thousands of rows and timeout after 120 seconds.

TOP 500 ordered by avg CPU desc is plenty for a grid view and prevents
the query from consuming unbounded memory on large installations.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Lazy-load server tabs and cap query grid results to TOP 500
The CAST(DECOMPRESS(...)) NOT LIKE N'WAITFOR%' filter was decompressing
query text on every row in query_stats and query_store_data just to skip
WAITFOR queries. WAITFOR has no plan and no meaningful stats — it only
matters in query snapshots (active sessions), where the filter remains.

On a 742K-row query_stats table, this was a significant contributor to
the 120-second query timeouts reported in #835.

The snapshot filters (report.query_snapshots) and MCP phased queries
are untouched — they filter after TOP on already-hydrated text.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
All three grid queries now use a 3-phase pattern:
1. Aggregate numerics into temp table (no DECOMPRESS)
2. Sum across lifetimes, rank TOP 500
3. OUTER APPLY to decompress text/plan for only the 500 winners

On a 742K-row query_stats table, this reduces DECOMPRESS calls from
742K to 500 — eliminating the 16+ minute query times reported in #835.

Matches the existing phased pattern used by the MCP query tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…press

Remove WAITFOR DECOMPRESS filters from query stats and query store
TDE moved to Standard Edition in SQL 2019, so dm_db_persisted_sku_features
no longer reports it as Enterprise-only. Add version check to give
version-appropriate licensing guidance instead of falsely claiming no
databases use TDE.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…on-awareness

Fix FinOps TDE recommendation on SQL Server 2019+
Port PS PRs #216, #217, #219, #224, #229, #230, #231 to PM.

PlanAnalyzer changes:
- Rule 5: Suppress for Key Lookups (point lookups mislead per-execution estimates)
- Rule 8: Enhanced parallel skew with batch mode sort detection and practical context
- Rule 9: Large memory grant shows top 3 consumers sorted by row count
- Rule 10: Key lookup overhaul — show output columns, check predicate filtering, softer advice
- Rules 11/12/29: Suppress on 0-execution nodes (operator never ran)
- Rule 11: I/O wait severity elevation when scan hits disk
- Rule 24: FormatNodeRef helper includes object name for data access operators
- Rule 26: Suppress when row goal prediction was correct, specific cause detection
- Wait stats: DescribeWaitType with full wait type coverage, multi-wait summary
- New helpers: GetWaitLabel, HasSignificantIoWaits, IdentifyRowGoalCause, FormatNodeRef
- GetOperatorOwnElapsedMs changed to internal for BenefitScorer access

BenefitScorer (new file):
- Stage 1: MaxBenefitPercent for operator-level rules (filter, spill, lookup, etc.)
- Stage 2: Wait stats benefit scoring with parallel allocation (Joe's formula)

PlanModels additions:
- MaxBenefitPercent and ActionableFix on PlanWarning
- WaitBenefit class and WaitBenefits list on PlanStatement

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…apr16

Sync PlanAnalyzer + BenefitScorer from PerformanceStudio (Apr 9-16)
…857)

On Azure SQL DB, some logins (e.g. Microsoft Dynamics 365 FO) are granted
access only to a specific user database and not to master. The three
collectors that enumerate databases via master — query_stats,
database_size_stats, file_io_stats — would fail the first time and
produce an empty screen.

GetAzureDatabaseListAsync now catches known access-denied/login-failed
errors from the master connection, caches the per-server decision, and
returns the connection's InitialCatalog as a single-element list. The
three callers already loop per-database, so single-DB mode works without
further changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…lback

Fall back to single-DB mode when Azure master is inaccessible (#857)
Phase 3 OUTER APPLY hydration of compressed query_text/plan_text was forcing
an Eager Index Spool over the full collect.query_stats table (and similar
for procedure_stats / query_store_data), which took 104 seconds on a
742K-row table in #835.

Changes:
- Remove CONVERT(binary(8), nvarchar-hash, 1) anti-pattern from OUTER APPLY
  WHERE clauses by keeping query_hash as native binary(8) in temp tables.
  query_hash is only converted to nvarchar(20) in the final output projection.
- Add three nonclustered indexes (install script and upgrade script):
    IX_query_stats_hash_lookup (query_hash, database_name, collection_time DESC)
    IX_procedure_stats_name_lookup (database_name, schema_name, object_name, collection_time DESC)
    IX_query_store_data_id_lookup (database_name, query_id, collection_time DESC)
- Indexes use SORT_IN_TEMPDB = ON and DATA_COMPRESSION = PAGE.
- ONLINE = ON is applied conditionally via dynamic SQL based on
  SERVERPROPERTY('EngineEdition') — Enterprise/Developer/Azure only, since
  Standard/Web/Express don't support online index operations.

Tested against CADelete's 742K-row table: Phase 3 went from 104s to
well under 1s (5s total for the full three-phase query).

Fixes #835

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…exes

Add nonclustered indexes for query/procedure/query store lookups
On Azure SQL Database, logins without access to master can't resolve
cross-database rows returned by sys.dm_exec_requests, which caused the
Live Snapshot button and the query snapshots collector to error in
D365FO-style environments (reported by @TrudAX in #857 after PR #858).

BuildQuerySnapshotsQuery now takes an isAzureSqlDatabase flag and emits
AND der.database_id = DB_ID() only when true. Boxed SQL Server, MI, and
elastic pool behavior is unchanged. The Live Snapshot button path gets
the flag through a new ServerTab constructor parameter wired from the
cached ServerConnectionStatus.SqlEngineEdition.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ure-scope

Scope query snapshots to current database on Azure SQL DB (#857)
- Chart X-axis prints the date line only on the first tick and on ticks
  where the date changes; all other ticks show time only. Format respects
  current culture (en-GB → dd/MM, de-DE → dd.MM, 24h clocks, etc.).
  Implemented as a DateTimeTicksBottomDateChange() extension in
  Lite/Helpers/AxesExtensions.cs and applied to every DateTimeTicksBottom
  call site in ServerTab and CorrelatedTimelineLanesControl.
- Server name no longer duplicated in the ServerTab header status line;
  ConnectionStatusText now shows just "Connecting..." / "Last refresh: ...".
- Chart tick label font bumped from 12 to 13 for readability.
- New SubTabItemStyle (thin accent underline, transparent background) in
  all three themes, applied to Queries / Memory / File I/O / Blocking /
  Perfmon / Running Jobs sub-TabControls so sub-tab selection no longer
  looks identical to main-tab selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…lish

Polish Lite chart axes and sub-tab styling
Dashboard polish (ports the same items merged to Lite in #862):
- New Dashboard/Helpers/AxesExtensions.cs with DateTimeTicksBottomDateChange(),
  culture-aware (dd/MM for en-GB, dd.MM for de-DE, 24h clocks, etc.). All 52
  call sites of DateTimeTicksBottom() across 10 files swapped to use it.
- TabHelpers.ApplyTheme + ReapplyAxisColors bump chart tick label font from
  12 to 13 so numbers read cleaner on wide charts.
- SubTabItemStyle added to Dark / Light / CoolBreeze themes: thin accent
  underline + transparent background instead of filled cyan, so sub-tabs
  don't look identical to main tabs when selected. Wired via
  ItemContainerStyle on 11 sub-TabControls (Overview's inner tabs,
  Collection Health's inner tabs, Locking, ConfigChanges, CurrentConfig,
  FinOps, Memory, ResourceMetrics ×2, SystemEvents, QueryPerformance).

LSP diagnostics cleanup (tracked work from chore/lsp-diagnostics-cleanup):
- Small nullability/warning fixes across Dashboard and Lite services,
  analysis helpers, and BenefitScorer / PlanAnalyzer.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…eanup

Port Lite chart/tab polish to Dashboard + LSP diagnostics cleanup
Root cause: the control wired `Unloaded += ...Dispose()` on the crosshair
manager, and WPF fires Unloaded for transient reasons (tab virtualization,
layout rebuilds, etc.), not just when the control is actually going away.
Dispose() clears the manager's lane list, after which ReattachVLines runs
over an empty list and the crosshair is gone permanently.

Changes:
- Remove the Unloaded → Dispose() handler in both Lite and Dashboard copies.
  The manager holds only managed state (a Popup + lane references) — GC
  will clean it up with the control.
- Remove the now-redundant `_isRefreshing` flag from CorrelatedCrosshairManager.
  The `lane.VLine == null` check in OnMouseMove is a sufficient "not ready"
  guard and is self-healing once VLines are recreated.
- Wrap ReattachVLines in a try/finally on the control side, with a new
  idempotent EnsureVLinesAttached() safety net that only creates VLines
  for lanes where they're still null.
- Make CreateVLine catch per-lane exceptions so one failing chart can't
  prevent the others from recovering.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
erikdarlingdata and others added 29 commits April 25, 2026 10:29
Pulls in two upstream bugfix releases relevant to Lite's collector pattern:
- 1.5.2 fixes unbounded row group growth on indexed tables under repeated
  load+insert cycles, and memory leaks/race conditions in prepared statements.
- 1.5.1 hardens WAL checkpoint marking, prevents memory corruption in
  concurrent TrimFreeBlocks, and improves Windows UTF-8/UTF-16 handling.

No storage version change within 1.5.x.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Patch-level upgrades only:
- Microsoft.Extensions.Configuration / Configuration.Json / Hosting / Logging:
  10.0.5 -> 10.0.7 (Dashboard, Lite)
- System.Text.Json: 10.0.5 -> 10.0.7 (Lite)
- ScottPlot.WPF: 5.1.57 -> 5.1.58 (Dashboard, Lite)

Microsoft.Data.SqlClient (6.1.4 -> 7.0.1) and ModelContextProtocol
(0.7.0-preview.1 -> 1.2.0) intentionally left out — both deserve dedicated
PRs after a focused review.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Bump safe NuGet packages: M.Extensions 10.0.7, System.Text.Json 10.0.7, ScottPlot 5.1.58
SqlClient 7.0 split Azure/Entra dependencies out of the core package, so
projects with Entra-Interactive auth paths (ActiveDirectoryInteractive)
also need the new Microsoft.Data.SqlClient.Extensions.Azure helper to
keep MFA login working at runtime.

Bumps to 7.0.1 across:
- Dashboard (+ Extensions.Azure 1.0.0)
- Lite (+ Extensions.Azure 1.0.0)
- Installer.Core (+ Extensions.Azure 1.0.0; transitive via Installer/InstallerGui/Installer.Tests)
- InstallerGui (+ Extensions.Azure 1.0.0)
- Installer (no Extensions.Azure — no Entra path)
- Installer.Tests (no Extensions.Azure — no Entra path)

Connection strings already set Encrypt explicitly everywhere, so the
4.0-era default flip is a no-op for this repo. SQL Server 2012+ remains
supported (so all of 2016/2017/2019/2022/2025 stay reachable).

Verified:
- All six projects build clean
- Installer.Tests: 46/46 passing against real SQL
- Lite.Tests: 257/257 passing against real SQL
- Dashboard and Lite launch and stay running

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Bump Microsoft.Data.SqlClient to 7.0.1 (+ Extensions.Azure for Entra)
Moves Dashboard and Lite from the 0.7.0-preview.1 SDK to the stable 1.2.0
release. The C# SDK landed 1.0 between these two points and the codebase
happens to use only the stable subset of the surface (attributes,
AddMcpServer/WithHttpTransport/WithTools<T>/MapMcp, DI-bound tool
methods) — none of the breaking changes (filter API rename, RequestContext
ctor, RunSessionHandler, binary-content types, McpServerHandlers,
legacy SSE) touch this repo.

154 [McpServerTool] sites and 39 [McpServerToolType] sites compile
unchanged. Zero source code changes — packaging only.

Verified:
- Dashboard and Lite build clean (0 errors)
- Lite launches; the in-process MCP server starts on :5151
- A POST initialize against the new Streamable HTTP transport returns
  the full server capabilities and tool descriptions

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Patch-level upgrade in the two test projects (Installer.Tests, Lite.Tests).
Test-only — no shipping code touched.

Verified: Installer.Tests 46/46, Lite.Tests 257/257 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
….4.0

Bump Microsoft.NET.Test.Sdk to 18.4.0
ExecuteInstallationAsync applied StandardTimeoutSeconds (300s) to every
batch in the main install script list. 98_validate_installation.sql runs
every enabled collector with @debug = 1 via a cursor in a single batch,
which on large databases (e.g. 7M-row collect.query_stats) takes longer
than 5 minutes and fails the entire install/upgrade.

ExecuteAllUpgradesAsync already uses UpgradeTimeoutSeconds (3600s) for
upgrades/{from}-to-{to}/ scripts (cfd7d6e, #538/#539). Apply the same
ceiling to the main install loop so files like 98_validate_installation
and large-table DDL get the same hour instead of the 5 minutes that was
only ever a comfortable default for small databases.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
- New "Purge Now" button on Manage Servers window with override modes
  (configured / 1 / 3 / 7 / custom / all). All option uses TRUNCATE.
- Right-click menu now mirrors all per-row actions (Edit, Toggle Favorite,
  Check Server Version, Purge Now, Remove) above existing copy/export.
- config.data_retention reworked: @retention_days = NULL respects per-collector
  schedule, 0 TRUNCATEs every collect.* table, N > 0 overrides every cutoff
  to N days. Old @truncate_all parameter removed; @retention_days = 0 covers
  that case. Truncate path snapshots row count before wiping for reporting.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…d-manage-servers-context-menu

Add Purge Now action to Manage Servers (closes #900)
The CPU alert and headline previously only used SQL Server's scheduler
ProcessUtilization, which answers "how busy are SQL's schedulers" — not
"is the box in trouble." On a noisy host that distinction matters.

Changes (Lite):
- Overview cards now show total non-idle CPU prominently with the SQL
  number alongside, e.g. "64% (SQL 60%)". Color tracks the alert metric.
- New CpuAlertMode enum (Total / SqlOnly), default Total. Settings has a
  "measured as" dropdown next to the CPU threshold.
- Alert evaluator and tray notifications use CpuPercentForAlert and label
  the value as "Total CPU" or "SQL CPU" so users know which metric tripped.
- Persists alert_cpu_mode in settings.json.

No data model or schema changes — sqlserver_cpu_utilization and
other_process_cpu_utilization were already collected; only the
presentation/alert layer needed work. Dashboard mirror is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…le-cpu-alert

Lite: surface total non-idle CPU + alert toggle (closes #899)
…manceStudio

Ports PerformanceStudio PRs #285 and #288 (net result; the temporary R/D/G
overlay from #285 was replaced by real icons in #288).

PlanIconMapper.GetIconName now takes optional storageType and logicalOp:
- Columnstore scans (Clustered/Index Scan with Storage="ColumnStore" on the
  Object element) route to the columnstore_index_* icons. Covers both CCI
  and NCCI for scan/delete/insert/update/merge.
- The three Parallelism subtypes (Repartition/Distribute/Gather Streams)
  route to their own icons instead of all sharing parallelism.png.

ShowPlanParser: the IconName assignment moves from immediately after PhysicalOp
parsing to after StorageType and LogicalOp are populated, so the new routing
has the inputs it needs.

Three new icons (MIT-licensed contribution from @rferraton via PS #288) added
to both Dashboard and Lite Resources/PlanIcons. The existing Resource glob
in each csproj picks them up automatically.

Last upstream sync from PerformanceStudio was 2026-04-16 (#856), scoped to
PlanAnalyzer + BenefitScorer. This is a follow-up scoped to plan-viewer
icon routing.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
When the Off preset, an Agent stoppage, or a server reboot pauses
collection for hours, the next run of query_stats / procedure_stats /
query_store would dump everything that accumulated during the gap into
their delta tables in one go. On query_stats specifically (issue #885),
that was enough to blow tempdb overnight.

Each of the three procs now reads MAX(config.collection_log.collection_time)
for its own collector_name (where status = SUCCESS) right after computing
the normal cutoff. If the gap to now exceeds 5x the configured frequency
(or 30 minutes, whichever is larger), it clamps the cutoff to SYSDATETIME()
so only forward-going data is collected on the resume run. NULL/0
frequency_minutes safely floors to 30 minutes.

XE-backed collectors (blocked_process_xml, deadlock_xml, system_health,
default_trace, trace_analysis) are bounded by their own @minutes_back /
@hours_back parameters and don't have the catch-up problem, so they're
left alone. Snapshot collectors (wait_stats, file_io_stats, etc) insert
one row per run regardless of gap and were never at risk.

Verified on sql2016/2017/2019/2022/2025: all three procs deploy cleanly,
heuristic fires on a 3-hour synthetic gap, stays quiet on normal runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…etection

Skip historical sweep when collectors resume after a gap (closes #892)
…tore-icons-from-ps

Sync columnstore + Parallelism subtype icons from PerformanceStudio
New config.collector_database_exclusions table holds a per-server list of
user databases to skip in per-database collectors. The eight collectors
that iterate sys.databases now honor it via a NOT EXISTS filter:
query_stats, query_store, procedure_stats, file_io_stats, waiting_tasks,
database_configuration, database_size_stats, server_properties.

System databases (master/tempdb/model/msdb) and the PerformanceMonitor
database itself remain hardcoded skips in the collectors and are not
represented in the exclusions list.

Dashboard side:
- Manage Servers window gets a new "Excluded Databases" button (and matching
  context menu entry) between Check Server Version and Purge Now.
- Default window width bumped 780 → 960 so all per-row buttons fit on first
  open without resizing.
- New ExcludedDatabasesDialog modal: live-queries sys.databases on the
  target, pre-checks current exclusions, hides system DBs, shows stale
  entries (in list but not on server) greyed and disabled with "(missing)".
  Save replaces the table contents transactionally.
- ServerManager.GetUserDatabasesAsync, GetCollectorDatabaseExclusionsAsync,
  and SaveCollectorDatabaseExclusionsAsync handle the connection plumbing.

Schema is added in install/01 (initial install) and mirrored in install/03
inside config.ensure_config_tables (resilient re-creation). No upgrade
folder needed — installer re-runs install/01 on existing installs and the
IF OBJECT_ID guard fires.

Verified end-to-end by running the CLI installer against sql2019, then
excluding hammerdb_tpcc and confirming database_size_stats_collector
skipped it (0 rows) while StackOverflow2010 was still collected.

Lite side will follow in a separate PR (different storage model — Lite
keeps the exclusion list in servers.json since it doesn't install on
targets).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…-exclusions

Per-database exclusions for collectors — Dashboard side (#887)
Lite-side companion to PR #905 — gives users a per-server way to skip
specific databases in the per-database collectors.

Storage: ExcludedDatabases (List<string>) added to ServerConnection,
persisted via the existing servers.json round-trip. Lite doesn't install
on targets, so the list lives client-side instead of in a remote
config.collector_database_exclusions table the way Dashboard does it.

Wiring: 9 collectors get the filter — query_stats, query_store,
procedure_stats, query_snapshots, file_io_stats, waiting_tasks,
database_configuration, database_scoped_configurations, database_size.
Azure-mode collectors that route through GetAzureDatabaseListAsync get
exclusions automatically since the helper now applies the filter
centrally.

Two SQL helpers on RemoteCollectorService:
- BuildDatabaseExclusionFilter — parameterized AND <col> NOT IN (@excl_db_N).
  Used by every collector whose query is plain (non-dynamic) T-SQL.
  No compatibility-level dependency (avoids OPENJSON / STRING_SPLIT which
  require compat 130+ on the connection database).
- BuildDatabaseExclusionLiteralClause — N'name' literals with single-quote
  escaping (and a forNestedDynamicSql=true mode that doubles the escape).
  Used by procedure_stats which builds @SQL then passes to sp_executesql,
  where parameter binding inside the dynamic statement is awkward.
  Names come from a user-picked checklist, so literal interpolation with
  proper escaping is safe.

UI: ManageServersWindow gets a new "Excluded Databases" button between
Edit and Delete. Right-click context menu now mirrors the buttons —
Edit / Excluded Databases / Delete on top, then the existing
Copy/Export items below a separator. New ExcludedDatabasesDialog
mirrors the Dashboard version: live-queries sys.databases, hides system
DBs, pre-checks current exclusions, shows stale entries (in list but not
on server) greyed with "(missing)" tag. Save calls
ServerManager.UpdateServer to persist via servers.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…abase-exclusions

Per-database exclusions for collectors — Lite side (closes #887)
Architecture overview of the collection pipeline for both Full and
Lite editions: the minute loop, dispatcher, collector shape, schedule
table, retention, Dashboard read path, and where-to-look-next
pointers for new contributors.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ll, version bumps

- New Installed Version column in Manage Servers for both editions:
  * Full: probes config.installation_history per server in parallel
  * Lite: shows the running app version (mirrors Full's header for consistency)
- Back-ports the Ellipse-with-DataTriggers status dot and right-aligned
  favorite star from Lite's server list to the Full Dashboard
- Bumps Version/AssemblyVersion/FileVersion/InformationalVersion to 2.9.0
  in Dashboard, Lite, Installer, and Installer.Core
- Rewrites CHANGELOG: fixes the 2.8.0 date to 2026-04-22 (was TBD), moves
  Off preset (#888) to 2.9.0 since #891 landed after the v2.8.0 tag, and
  adds the full 2.9.0 entry covering the post-2.8.0 PR set
- Updates README's DuckDB version reference (1.5.0 -> 1.5.2)

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ersion-column

Release v2.9.0: Manage Servers version column, Lite-style indicators in Full, prep
# Conflicts:
#	CHANGELOG.md
#	Dashboard/Dashboard.csproj
#	Dashboard/Themes/CoolBreezeTheme.xaml
#	Dashboard/Themes/DarkTheme.xaml
#	Dashboard/Themes/LightTheme.xaml
#	Installer.Core/Installer.Core.csproj
#	Installer/PerformanceMonitorInstaller.csproj
#	Lite/Controls/ServerTab.xaml
#	Lite/Controls/ServerTab.xaml.cs
#	Lite/PerformanceMonitorLite.csproj
#	Lite/Services/RemoteCollectorService.QuerySnapshots.cs
#	Lite/Services/RemoteCollectorService.cs
Merge main into dev to resolve v2.8.0 squash divergence (release v2.9.0 prep)
@erikdarlingdata erikdarlingdata merged commit 531dae3 into main Apr 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants