Release v1.14.0#374
Merged
Merged
Conversation
…ded" (#367) Loading a plan from the Query Store grid could blank to "No Plan Loaded" while the grid vanished, with no indication why. Two compounding causes: - PlanViewerControl.LoadPlan treated a failed parse identically to an empty plan: it never inspected ParsedPlan.ParseError (which ShowPlanParser sets instead of throwing) and just showed the empty state on zero statements. - AddPlanTab unconditionally added and selected the new plan tab even when the load failed, navigating away from the grid to a blank tab. (Commit e8e5a21's parser hardening was the regression vector: it converted a tree-walk exception on a bad plan from a visible crash into a silent zero-statement ParseError, producing exactly this symptom.) LoadPlan now returns bool and exposes LastLoadError, distinguishing blank/NULL plan XML, a parse error (surfaced verbatim), and parsed-but-no-statements. AddPlanTab no longer switches away on failure -- it keeps the grid active and leaves a persistent status explaining why. Clear() resets the empty state so a reused viewer cannot show a stale error. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…VSSDK 18) (#369) Sweep of every outdated package across all 8 projects to its current stable, holding back only the two that are genuinely blocked. App: - Avalonia + Desktop/Themes.Fluent/Fonts.Inter/Diagnostics 11.3.14 -> 11.3.17 - Meziantou.Framework.Win32.CredentialManager 1.7.18 -> 2.0.1 - ModelContextProtocol (+ AspNetCore) 1.3.0 -> 1.4.0 - Microsoft.SqlServer.TransactSql.ScriptDom 180.6.0 -> 180.37.3 - Velopack 0.0.1298 -> 1.2.0 (+ pin vpk CLI to 1.2.0 in release.yml) - TextMateSharp.Grammars 2.0.3 -> 2.0.4 - AvaloniaEdit.TextMate.Grammars 0.10.12 -> 0.10.12.1 - SkiaSharp.NativeAssets.Linux 3.119.2 -> 3.119.4 Core: - Meziantou.Framework.Win32.CredentialManager 1.7.18 -> 2.0.1 - Microsoft.SqlServer.TransactSql.ScriptDom 180.6.0 -> 180.37.3 Cli: System.CommandLine 2.0.7 -> 2.0.9 Web: Microsoft.AspNetCore.Components.WebAssembly (+ DevServer) 10.0.0 -> 10.0.9 PlanShare: Microsoft.Data.Sqlite 10.0.5 -> 10.0.9 Tests: Microsoft.NET.Test.Sdk 18.5.1 -> 18.6.0, coverlet.collector 10.0.0 -> 10.0.1 SSMS VSIX: Microsoft.VisualStudio.SDK -> 17.14.40265, Microsoft.VSSDK.BuildTools 17.11.435 -> 17.14.2142 (latest 17.x; 18.x is un-restorable and targets VS 18) Held back (blocked, not skipped): - Avalonia 12.x family — ScottPlot.Avalonia has no v12 build (charts render blank); migration is done and parked on upgrade/avalonia-12. - Microsoft.VSSDK.BuildTools 18.x — un-restorable from nuget.org. Already-current (no change): Microsoft.Data.SqlClient 7.0.1, SqlClient.Extensions.Azure 1.0.0, ScottPlot.Avalonia 5.1.58, Avalonia.AvaloniaEdit/TextMate 11.4.1, Avalonia.Controls.DataGrid 11.3.13, xunit.v3 3.2.2, xunit.runner.visualstudio 3.1.5, Microsoft.NETFramework.ReferenceAssemblies 1.0.3. Verified: solution Release build clean (0 errors, no NuGet conflicts), PlanShare builds, SSMS restores on 17.x, 77/77 tests pass, app launches and renders. Velopack/Meziantou majors need no code changes. Full install->update->relaunch validation happens at the next release cut (inherent to any updater bump). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…370) (#371) The InstallationTarget version under Microsoft.VisualStudio.Ssms is the SSMS product version, not the VS shell version. The old [17.0,) installed fine (21 and 22 are both >= 17) but the SSMS Gallery rendered the lower bound literally as "Supports SSMS 17 SSMS 18". Switch to [21.0,23.0) (the form ErikEJ's SqlCeToolbox uses) so the gallery shows "SSMS 21 / SSMS 22", and clarify the description. Verified end to end: rebuilt the VSIX and confirmed SSMS 22's VSIXInstaller accepts [21.0,23.0) (uninstall + reinstall both exit 0). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-operator "own time" CPU/elapsed calculation was implemented separately in the desktop plan viewer (PlanViewerControl) and the web plan viewer (Index.razor), with the desktop version handling exchange operators via worker-thread data and the web version lacking that. Consolidate both onto a single Core service (NodeTimeAttribution) ported from the more-correct desktop implementation, so desktop and web report identical operator timings. The web's node coloring/timing display now matches the desktop, including parallelism exchange handling. The analysis pipeline (PlanAnalyzer.Timing) and the OperatorResult-based text exporter (TextFormatter) keep their own implementations by design. NodeTimeAttribution.cs is linked into PlanViewer.Web (Blazor WASM links Core sources individually rather than referencing the assembly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Index.razor was a 2342-line god file mixing a route page, a 1270-line operator-properties inspector, insights/warnings panels, share modals, and all the HTTP/JSON share plumbing. Break it apart: - OperatorPropertiesPanel.razor — the ~1270-line, ~40-section operator inspector, parameterized by Node/StmtPlan/Plan/IsRoot/OnClose. - InsightsPanel.razor / WarningsStrip.razor — the runtime/indexes/params/ waits cards and the warnings strip. - PlanViewFormat — stateless formatting/predicate helpers shared across the page and components, imported via `@using static` so call sites are unchanged. - IPlanShareService / PlanShareService — the share/delete/load HTTP+JSON logic, registered in DI with a single shared HttpClient instead of a `new HttpClient()` per call. UI concerns (clipboard, state, errors) stay in the page; the service throws PlanShareException with user-facing text. Index.razor drops from 2342 to 591 lines with no behavior change (markup moved verbatim, identifiers reparameterized). Remaining optional splits (PlanInputView, ShareDialog, PlanTree) deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Properties.cs was a 1772-line catch-all whose name said "Properties" but which actually built six different right-side panels. Peel the non-core panels into partials of the same class (pure mechanical move, no behavior change): - PlanViewerControl.MissingIndexes.cs — ShowMissingIndexes - PlanViewerControl.Parameters.cs — ShowParameters + param-cell/annotation builders + unresolved-variable detection - PlanViewerControl.WaitStats.cs — ShowWaitStats + wait categorization - PlanViewerControl.RuntimeSummary.cs — ShowRuntimeSummary + ShowServerContext Properties.cs keeps the General/Object/etc. ShowPropertiesPanel core plus the shared AddPropertySection/AddPropertyRow builders and close handlers, dropping from 1772 to 1091 lines. (The per-node time-attribution helpers already moved to Core's NodeTimeAttribution earlier in this branch.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both controls were unsplit god files — QueryStoreHistoryControl in particular was the team's outlier, while every comparable sibling control already uses feature partials. Bring them in line (pure mechanical moves, no behavior change): QueryStoreHistoryControl.axaml.cs (1104 -> 321): .Fetch.cs — LoadHistoryAsync .Grid.cs — color-indicator plumbing + grid<->chart selection sync .Legend.cs — legend panel + plan highlighting .Chart.cs — chart drawing, smart X axis, dot highlighting .Selection.cs — pointer/box selection + hover tooltip .PlanLoad.cs — context menu + load-plan-from-selection QueryStoreOverviewControl.axaml.cs (823 -> 250): .Donut.cs — donut/arc geometry + popup + legend .WaitStatsChart.cs — stacked wait-stats chart .BarCards.cs — metric bar cards (+ shared _dbColorMap) Section divider comments travel with their sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CREATE INDEX / CREATE TABLE scripting was duplicated in
PlanViewerControl.Schema.cs and QuerySessionControl.Schema.cs, and the two
copies had drifted: the query-session copy used a BracketName() helper for
safe identifier quoting (and richer comments) while the plan-viewer copy
used naive [{name}] interpolation that would double-bracket an already
bracketed identifier.
Consolidate onto the query-session (more-correct) version as a single,
unit-testable Core DdlScripter and point both call sites at it. The plan
viewer's schema scripting now also gets safe identifier bracketing —
identical output for normal names, correct for already-bracketed ones.
Adds DdlScripterTests covering clustered PK, nonclustered w/ include+
filter+options, columnstore, the bracket-safety behavior, and table/column
+ computed-column output. 77 -> 85 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ParseRelOp was a single ~760-line method. Extract the two largest, self-contained sections into private helpers, leaving ParseRelOp as a ~130-line orchestrator (node init -> operator props -> RelOp-level props -> runtime info -> icon -> recurse children): - ParseOperatorProperties(node, relOpEl, physicalOpEl) — the operator- specific property parsing (the former `if (physicalOpEl != null)` block). - ParseRuntimeInformation(node, relOpEl) — actual-plan per-thread runtime counter aggregation and PerThreadStats. Pure mechanical extraction; the 85 parser/analyzer tests confirm identical output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AnalyzeCommand and QueryStoreCommand duplicated their connection/credential resolution, server-metadata fetch, the parse->analyze->score pipeline, and the json/text/both result-file writing. Several copies had drifted. - CliConnectionResolver: IsAzureSqlDb, BuildServerConnection (auth-type resolution + SQL-credential existence check), and the non-fatal FetchServerMetadataAsync. - PlanAnalysisRunner: Analyze (parse + analyze + score, branching on serverMetadata) and WriteResultFilesAsync (warningsOnly + json/text/both). Both commands now share these. querystore picks up AnalyzeCommand's fail-fast "no credential found" check (wrapped in the same graceful try/catch) instead of proceeding to a cryptic downstream SQL error. Offline analyze, live capture, and querystore behavior otherwise unchanged; verified the offline analyze path end to end on a sample plan. 85 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Snapshots the analyzer's complete warning output (type, severity, and full message text, in order) across all 37 committed test plans into WarningBaseline.txt. This locks down current behavior so the upcoming extraction of AnalyzeNode/AnalyzeStatement's inline rules into methods can be proven behavior-preserving — any drift in the warning set fails the test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AnalyzeNode (~770 lines, 25 inline rules) and AnalyzeStatement (~440, 12 rules) were two mega-methods. Turn each into a thin dispatcher that calls one named method per rule (Rule01_FilterOperator, Rule07_SpillSeverity, Rule38_StandardEditionDop, ...), preserving the exact execution order so ordering-dependent behavior is unchanged: - The spill-severity rule still runs in position and mutates the existing warning collection as before. - Rule 11 (scan residual) depends on Rule 12's non-SARGable determination; that shared local is now a pure GetNonSargableReason(node, cfg) helper both rules call, rather than a strategy registry (which the review warned against). No behavior change: the WarningBaseline golden master (full warning set across all 37 plans) and the per-rule tests all pass unchanged. 86 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Break up god files + dedup drifted code
Compress-Archive on the windows-latest runner can't store Unix file-mode bits, so the self-contained apphost (PlanViewer.App) extracted as non-executable on Linux/macOS and the app would not launch. .NET's ZipArchive is no better: on Windows it stamps the archive host byte as Windows (create_system=0), so unzip/macOS ignore the mode even when ExternalAttributes carries 0755. Add .github/scripts/zip_with_exec.py (Python zipfile with create_system=3) to zip the Linux flat output and both macOS .app bundles, marking the apphost (and createdump if present) 0755. It hard-fails if the apphost is missing so a broken archive fails the release loudly. Wire it into release.yml and nightly.yml; Windows stays on Compress-Archive (no Unix bit needed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add CFBundleDocumentTypes to Info.plist so Finder shows the app icon for .sqlplan files and routes them to Performance Studio via the Open With menu. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ":P0" format is culture-sensitive in the space before the percent sign (en-US -> "100%", InvariantCulture -> "100 %"). Warning text therefore differed between en-US dev machines and CI runners that default to InvariantCulture, breaking the golden-master WarningCharacterizationTests (baseline recorded "100%", ubuntu produced "100 %").
Format the three percents as "{x * 100:N0}%" with a literal percent sign. N0 preserves the digits and group separators :P0 produced on en-US, so the committed baseline stays valid; the literal % drops the culture-dependent space, making output identical on every platform. Verified: full Core suite passes under default and DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preserve Unix execute bit in Linux/macOS release zips
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v1.14.0
Promotes everything on
devsince v1.13.0 tomain, which triggers the signed release build and publish.Highlights
Cross-platform packaging fix (prompted this release)
.github/scripts/zip_with_exec.py, so the downloaded app launches without a manualchmod +x(Preserve Unix execute bit in Linux/macOS release zips #373)..appbundle registers the.sqlplandocument type (Finder icon + "Open With").:P0rendered100%on en-US but100 %on InvariantCulture/CI).Also included since 1.13.0
Index.razor), extract shared Core services (NodeTimeAttribution, DdlScripter, CLI helpers), splitShowPlanParser.ParseRelOp, and add a golden-master warning characterization test (Break up god files + dedup drifted code #372).Release mechanics
On merge,
release.ymlbuilds all four platforms, signs the Windows build via SignPath, packs Velopack + flat zips (Linux now with the execute bit) + macOS.appbundles, and publishes thev1.14.0release.🤖 Generated with Claude Code