Link every warning to the operator it came from (#440) - #446
Merged
Conversation
A warning did not record where it came from, so on a large plan there was no way to get from a finding to the thing that produced it. pgfiore's framing was exactly right: "if the plan is huge and the warning origin is murky, it would help to click a warning and be beamed up to a specific step." PlanWarning.OriginNodeIds carries it, and the interesting half of this feature is knowing when to say nothing. Three honest answers, not one: - A key lookup came from exactly one operator. - A table variable warning came from every operator that touched one, which on a real plan is several. That rule already walked the tree and knew precisely which ones; it threw the answer away before emitting. It does not any more. - "High Compile CPU" happened before a single row was read, and SQL Server reports "UDF Execution" at the statement level only. Those have NO operator origin and now say so, so the UI offers no link rather than one that goes somewhere arbitrary. Sending a reader to the wrong operator is worse than sending them nowhere, because they would believe it. Operator warnings are stamped in ONE place, at the end of AnalyzeNode, rather than at the 26 sites that add one - the same reasoning as the provenance stamp in #439 and the ceiling in #438. A rule you have to remember at every construction site is a rule that eventually gets forgotten, and the once it is forgotten the UI quietly drops a link that existed. It only fills what a rule left empty, so a rule that knows better keeps its own answer. The scope call worth reviewing. The pop-up the issue is about shows STATEMENT warnings, and few of those can attribute to an operator - so linking only those would not have served the huge-plan case that motivated the request at all. The warnings that do have origins are the operator ones, and until now the only way to see one was to have already clicked the operator carrying it, which is no help when you do not know which operator to click. So the statement panel also gains an "Operator Warnings" section indexing every warning in the tree, each one a link. Nothing is removed from the per-operator panel; this is an index into it. It is collapsed by default because on a large plan it is the longest section in the panel and expanding it would push the statement's own details off screen, which is the opposite of the problem being solved. The tree walk lives in Core as WarningIndex rather than beside the panel that renders it: it is a walk over Core's own models with nothing UI about it, and there it can be tested without standing up Avalonia. It uses an explicit stack rather than recursion, because a deep plan is precisely the case this feature exists for and #430 was a crash caused by assuming operator trees are shallow. CLI output contract: "origin_node_ids" is additive on every warning, so ExpectedCompactOutputSha256 is rolled - second time, both additive, both deliberate. Verified that origin_node_ids is the ONLY new key rather than assuming it: the full key set on a warning is otherwise unchanged. WarningBaseline.txt does not move. It digests type, severity and message, none of which changed, so no committed plan's verdict is affected. Tested: 314 total, 312 passed, 0 failed. The new tests pin both directions - that across every committed plan no operator warning is left without an origin or points away from its own node, that the table variable warning names the operators that touch one, and that High Compile CPU and UDF Execution claim none. The index is compared against an independent recursive walk rather than a hand-written count, so it cannot drift as fixtures are added. Not verified: the click itself. This box has no reachable display session, so screencapture fails and I could not watch a warning navigate. The app was launched on a plan carrying both kinds of warning and ran clean with no exceptions, and the logic underneath is covered, but someone with a screen should confirm the scroll lands where it should before this is trusted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Reviewed the diff. This is a clean, well-scoped change — traced the data flow end to end and didn't find correctness issues:
No blocking findings. Nice attention to the "sending a reader to the wrong operator is worse than nowhere" invariant, and it's actually enforced by tests, not just asserted in the description. Note: I wasn't able to run |
5 tasks
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.
Implements #440. Warnings now know which operator they came from, and the statement panel links to it.
The design question is when not to link
PlanWarning.OriginNodeIdsis a list, because there are three honest answers and they're genuinely different:High Compile CPUhappened before a single row was read, and SQL Server reportsUDF Executionat the statement level only. Those have no operator origin and now say so, so the UI offers no link rather than one that goes somewhere arbitrary.Sending a reader to the wrong operator is worse than sending them nowhere, because they'd believe it. Both directions are pinned by tests.
Stamped in one place
Operator warnings get their origin at the end of
AnalyzeNode, not at the 26 sites that add one — same reasoning as the provenance stamp in #439 and the ceiling in #438. A rule you must remember at every construction site is a rule that eventually gets forgotten, and the once it's forgotten the UI quietly drops a link that existed. It only fills what a rule left empty, so a rule that knows better keeps its own answer.Scope call worth reviewing
The pop-up the issue is about shows statement warnings, and few of those can attribute to an operator. Linking only those would not have served the huge-plan case that motivated the request at all.
The warnings that do have origins are the operator ones — and until now the only way to see one was to have already clicked the operator carrying it, which is no help when you don't know which operator to click. That's the chicken-and-egg pgfiore is describing.
So the statement panel also gains an Operator Warnings section indexing every warning in the tree, each one a link. Nothing is removed from the per-operator panel; this is an index into it. Collapsed by default, because on a large plan it's the longest section in the panel and expanding it would push the statement's own details off screen — the opposite of the problem being solved.
If you'd rather ship only the statement-warning links, drop that block and the rest stands.
Placement
The tree walk lives in Core as
WarningIndex, not beside the panel that renders it: it's a walk over Core's own models with nothing UI about it, and there it can be tested without standing up Avalonia. Explicit stack rather than recursion — a deep plan is precisely the case this feature exists for, and #430 was a crash caused by assuming operator trees are shallow.Contract
origin_node_idsis additive on every warning, soExpectedCompactOutputSha256is rolled — second time, both additive, both deliberate. I verified it's the only new key rather than assuming: the full key set on a warning is otherwise unchanged.WarningBaseline.txtdoes not move — it digests type, severity and message, none of which changed, so no committed plan's verdict is affected.Tests
314 total, 312 passed, 0 failed. New tests pin that across every committed plan no operator warning is left without an origin or points away from its own node; that the table variable warning names the operators that touch one; and that
High Compile CPUandUDF Executionclaim none. The index is compared against an independent recursive walk rather than a hand-written count, so it can't drift as fixtures are added.Verified in the app
Confirmed by hand on two plans, since the click is the part no test here can reach:
excellent-parallel-spill.sqlplan— the Operator Warnings (12) index, each entry navigating to its operator; the 8 plan-wide statement warnings correctly showing no arrow.table_variable_plan.sqlplan— the one statement warning that can attribute, carrying its link.Navigation and scroll-into-view behave. Erik checked it on a real display; I could not, because this box has no reachable display session (
screencapturefails), which is why the app was launched for him rather than screenshotted.🤖 Generated with Claude Code