Release v1.16.0 - Query Store search and filtering#394
Merged
Conversation
A self-contained Agent Skill that teaches any Claude agent to read a SQL Server
execution plan without needing PerformanceStudio installed. Nothing in it refers
to this repo, so the directory copies out cleanly.
The valuable content is negative knowledge -- the confident-sounding conclusions
that are wrong. Cost percentages are estimates even in actual plans. Row-mode
operator times are cumulative, so ranking by raw ActualElapsedms ranks by depth
and always crowns the root. Estimated vs actual rows must be normalized by
ActualExecutions or the inner side of every nested loop looks catastrophically
underestimated. Missing-index requests emit equality columns in arbitrary order
and ignore existing indexes.
Contents:
SKILL.md seven-step triage, ordered to establish ground truth before
forming an opinion, plus a "not evidence" list
scripts/ extract.py flattens a .sqlplan into a digest; --node drills
into one operator; --sql recovers full statement text
references/ timing, cardinality, warnings, parallelism, indexes, operators,
and a no-Python fallback
extract.py exists because .sqlplan files are UTF-16 (so grep silently matches
nothing), some declare an encoding they don't use, and a trivial plan is 120 KB
while the largest fixture here is 7 MB. It mirrors PlanAnalyzer.Timing.cs and
NodeTimeAttribution.cs for self-time attribution: batch mode reports standalone
times, row mode cumulative, exchange operators accumulate downstream wait time,
and parallel self-time must subtract within a thread rather than across threads.
Validated by seven agents with no prior context, run against the 299 example
plans. All reached correct conclusions, including on three traps: a plan with
nothing wrong, a no-join-predicate warning that was a false alarm, and an
estimated-only plan where the correct answer is to decline and ask for an actual
plan. Their findings fixed a real bug (Table Spool matched the Index Spool
detection, so update plans were told they needed an index), a documentation hole
that would have produced a wrong cross-join diagnosis, two factual errors about
scalar-UDF parallelism reasons and parallel self-time arithmetic, and an answer
key that had leaked from a test fixture into SKILL.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add portable query-plan-analysis skill
Claude Code never discovered this directory. Skills are only loaded from ~/.claude/skills/, .claude/skills/, or a plugin bundle -- a bare skills/ path at a repo root does nothing. The copy added in #385 was therefore nine files of documentation that no agent could load, and it had already drifted: its SKILL.md still resolves scripts/extract.py against a repo path, which is wrong once the skill is installed as a plugin and its directory is unguessable. The skill now ships from erikdarlingdata/claude-plugins as a Claude Code plugin: /plugin marketplace add erikdarlingdata/claude-plugins /plugin install query-plan-analysis@erikdarling It was moved out rather than promoted here because reaching this repo's default branch requires a dev -> main PR, which auto-fires release.yml and demands a version bump -- shipping a Velopack auto-update to every user for a change containing no application code. One coupling to keep in mind: the skill's references/timing.md is transcribed from src/PlanViewer.Core/Services/PlanAnalyzer.Timing.cs and NodeTimeAttribution.cs (row-mode cumulative vs batch-mode standalone times, exchange operators accumulating downstream waits, per-thread rather than cross-thread subtraction). If those semantics change, update the skill repo; nothing will catch the drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-copy Remove inert skills/ copy of query-plan-analysis
…h subtrees
Two bugs in self-time attribution, both of which inflate an operator until it is
reported as the hottest in its plan. Found while building a portable query-plan
skill that mirrored this code, and confirmed against the fixture set.
1. THREAD 0 IS THE COORDINATOR.
In a parallel plan thread 0 carries no rows, and its ActualElapsedms is the wall
clock of the whole parallel branch. GetPerThreadOwnElapsed iterated every thread
including it, so any operator near the top of a parallel branch was handed the
branch's entire duration as its own work, and its self CPU underflowed to zero
because the coordinator burns none. ShowPlanParser also took the node-level
elapsed as the max across all threads, including the coordinator.
Measured across the .internal/examples corpus: of 1,014 non-exchange parallel
operators, the coordinator reports rows in 0 cases and elapsed in 65. Those 65
are precisely the operators that win a ranking by self time.
Serial plans have a single thread numbered 0, which IS a worker, so thread 0 is
only excluded when other threads exist.
2. THE PARALLEL PATH DID NOT LOOK THROUGH BATCH SUBTREES OR PASS-THROUGHS.
GetSerialOwnElapsed subtracts GetEffectiveChildElapsedMs, which looks through
Compute Scalar pass-throughs and sums contiguous batch zones. GetPerThreadOwnElapsed
subtracted each child's raw per-thread value instead, handling only Parallelism
children. So:
- a batch-mode child reports STANDALONE time, so only its topmost value came
off and the rest of the zone stayed inside the parent;
- a pass-through child carries no runtime stats, so zero came off and the
parent absorbed the entire subtree beneath it.
On cross-apply-point-in-time-slow.sqlplan a row-mode Nested Loops above a batch
Compute Scalar (0ms) hiding a batch Hash Aggregate (45,683ms) reported 45,737ms
of self time and led the ranking. Its real self time is 1ms, and the aggregate's
time was double-counted into both rows.
The same helpers now serve both paths, for elapsed and CPU. NodeTimeAttribution
(the display path) gets the matching batch-subtree summation so the graphical
plan and the analyzer agree.
TESTS
New OperatorSelfTimeTests, with a small synthetic fixture that encodes both traps
in one shape. Three of the six fail against the unfixed code, including
ParallelPassThroughChildDoesNotInflateItsParent, which uses the EXISTING
join_or_clause_plan fixture -- this was live on real plans.
PlanViewer.Core.csproj now grants InternalsVisibleTo the test project;
PlanAnalyzer.Timing is internal but is the source of every operator timing the UI
and BenefitScorer report, and it was untested.
Warning baseline: one line changes. join_or_clause_plan's Sort drops from
19,602ms to 18,580ms of self time -- the Compute Scalar subtree it had absorbed.
The warning still fires; the number is now correct. No warnings appear or
disappear on any other plan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tor-and-batch Fix operator self-time: exclude coordinator thread, look through batch subtrees
- QueryStoreFilter.QueryTextSearch + shared BuildQueryTextSearchPattern helper (sp_QuickieStore default semantics: trim, auto-wrap %, wildcards stay live) - Parameterized EXISTS predicate applied pre-TOP-N in all three Query Store SQL builders (flat, grouped-by-hash, grouped-by-module) - Desktop: Query Text option in the Search-by combo - MCP: additive query_text_search param on get_query_store_top - CLI: additive --query-text-search option - Tests: helper pattern matrix + ParseExecutionType backfill (22 new cases) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…arch Add Query Store query-text search filter
Core: - QueryStoreFilter: MinExecutions, MinDurationMs, MinCpuMs (window-wide per-plan averages), QueryTextSearchNot, EscapeBrackets, and four include/ignore query-id/plan-id lists; strict ParseIdList (ordered dedupe, throws on garbage); BuildQueryTextSearchPattern escapeBrackets overload (escapes [ ] _ for ESCAPE '\', % stays live) - All three SQL builders (flat, grouped-by-hash, grouped-by-module) gain the new clauses pre-TOP-N, fully parameterized; rn=1 relaxed when plans are explicitly included; unset path renders byte-identical SQL - QueryStoreServerFilterState: UI-free, unit-testable filter model (typed setters, ordered chips, 1:1 BuildFilter projection) Desktop: - Collapsible Server Filters panel (thresholds, text include/exclude, escape toggle, id lists) with transactional Apply - Chip strip showing every criterion that shaped the fetch; chips are removable and re-fetch - Column-filter popups gain 'Search server' promotion on mapped columns (Executions/AvgCpu/AvgDuration/ids/hashes/text) with operator validation at click; totals deliberately not promotable - Search-by commits become chips; Clear clears all server filters; panel expanded state persisted MCP/CLI: - Nine additive get_query_store_top params and query-store options Tests: 59 new (escape-mode matrix, ParseIdList contract, filter-state model) - 182 total passing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add Query Store server-side filter panel, chips, and pre-filters
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump version to 1.16.0
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.16.0
Query Store
Plan analysis
Tooling
Version bumped to 1.16.0 in Directory.Build.props and the SSMS extension (AssemblyInfo.cs + vsixmanifest) (#393).
Merging this PR triggers the release workflow: GitHub release with notes, signed Windows build, macOS/Linux builds, SSMS VSIX + gallery upload, and Velopack packages.
🤖 Generated with Claude Code