From b3e5ed2d6d986fab5b1ae3efd6d0dec84c30109d Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:08:44 -0400 Subject: [PATCH] Version 1.0.0: bug fixes and version bump - Fix Runtime line indentation in advice window (timing detector was catching key-value lines containing "ms CPU") - Fix tooltip to always show both Est. Rebinds and Est. Rewinds when section is visible - Bump version from 0.9.0 to 1.0.0 (App, CLI, Info.plist) - Add NoJoinPredicate test with missing-join-predicate fixture plan Co-Authored-By: Claude Opus 4.6 --- .../Controls/PlanViewerControl.axaml.cs | 5 +++-- src/PlanViewer.App/Info.plist | 4 ++-- src/PlanViewer.App/PlanViewer.App.csproj | 2 +- .../Services/AdviceContentBuilder.cs | 4 +++- src/PlanViewer.Cli/PlanViewer.Cli.csproj | 2 +- .../PlanViewer.Core.Tests/PlanAnalyzerTests.cs | 16 ++++++++++++++++ .../Plans/missing-join-predicate.sqlplan | Bin 0 -> 20422 bytes 7 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 tests/PlanViewer.Core.Tests/Plans/missing-join-predicate.sqlplan diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index e96024d..3be8573 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -1783,8 +1783,9 @@ private object BuildNodeTooltipContent(PlanNode node, List? allWarn || node.ActualRebinds > 0 || node.ActualRewinds > 0) { AddTooltipSection(stack, "Rebinds / Rewinds"); - if (node.EstimateRebinds > 0) AddTooltipRow(stack, "Est. Rebinds", $"{node.EstimateRebinds:N1}"); - if (node.EstimateRewinds > 0) AddTooltipRow(stack, "Est. Rewinds", $"{node.EstimateRewinds:N1}"); + // Always show both estimated values when section is visible + AddTooltipRow(stack, "Est. Rebinds", $"{node.EstimateRebinds:N1}"); + AddTooltipRow(stack, "Est. Rewinds", $"{node.EstimateRewinds:N1}"); if (node.ActualRebinds > 0) AddTooltipRow(stack, "Actual Rebinds", $"{node.ActualRebinds:N0}"); if (node.ActualRewinds > 0) AddTooltipRow(stack, "Actual Rewinds", $"{node.ActualRewinds:N0}"); } diff --git a/src/PlanViewer.App/Info.plist b/src/PlanViewer.App/Info.plist index f8cb129..2cd39e7 100644 --- a/src/PlanViewer.App/Info.plist +++ b/src/PlanViewer.App/Info.plist @@ -11,9 +11,9 @@ CFBundleExecutable PlanViewer.App CFBundleVersion - 0.9.0 + 1.0.0 CFBundleShortVersionString - 0.9.0 + 1.0.0 CFBundlePackageType APPL NSHumanReadableCopyright diff --git a/src/PlanViewer.App/PlanViewer.App.csproj b/src/PlanViewer.App/PlanViewer.App.csproj index 0f5548a..0cea721 100644 --- a/src/PlanViewer.App/PlanViewer.App.csproj +++ b/src/PlanViewer.App/PlanViewer.App.csproj @@ -5,7 +5,7 @@ enable app.manifest true - 0.9.0 + 1.0.0 Erik Darling Darling Data LLC Performance Studio diff --git a/src/PlanViewer.App/Services/AdviceContentBuilder.cs b/src/PlanViewer.App/Services/AdviceContentBuilder.cs index d59b7f4..35f1c37 100644 --- a/src/PlanViewer.App/Services/AdviceContentBuilder.cs +++ b/src/PlanViewer.App/Services/AdviceContentBuilder.cs @@ -215,7 +215,9 @@ public static StackPanel Build(string content) } // Expensive operator timing lines: "4,616ms CPU (61%), 586ms elapsed (62%)" - if (trimmed.Contains("ms CPU") || trimmed.Contains("ms elapsed")) + // Must start with a digit to avoid catching "Runtime: 1,234ms elapsed, 1,200ms CPU" + if ((trimmed.Contains("ms CPU") || trimmed.Contains("ms elapsed")) + && trimmed.Length > 0 && char.IsDigit(trimmed[0])) { panel.Children.Add(CreateOperatorTimingLine(trimmed)); continue; diff --git a/src/PlanViewer.Cli/PlanViewer.Cli.csproj b/src/PlanViewer.Cli/PlanViewer.Cli.csproj index f07b1ac..c9f97e0 100644 --- a/src/PlanViewer.Cli/PlanViewer.Cli.csproj +++ b/src/PlanViewer.Cli/PlanViewer.Cli.csproj @@ -11,7 +11,7 @@ enable PlanViewer.Cli planview - 0.9.0 + 1.0.0 Erik Darling Darling Data LLC Performance Studio diff --git a/tests/PlanViewer.Core.Tests/PlanAnalyzerTests.cs b/tests/PlanViewer.Core.Tests/PlanAnalyzerTests.cs index 5ad756f..61cd337 100644 --- a/tests/PlanViewer.Core.Tests/PlanAnalyzerTests.cs +++ b/tests/PlanViewer.Core.Tests/PlanAnalyzerTests.cs @@ -743,4 +743,20 @@ public void ParallelSkew_DetectedInSkewedPlan() } return null; } + + // --------------------------------------------------------------- + // NoJoinPredicate: verify it flows through to TextFormatter output + // --------------------------------------------------------------- + + [Fact] + public void NoJoinPredicate_AppearsInTextFormatterOutput() + { + var plan = PlanTestHelper.LoadAndAnalyze("missing-join-predicate.sqlplan"); + var analysis = PlanViewer.Core.Output.ResultMapper.Map(plan, "file"); + var text = PlanViewer.Core.Output.TextFormatter.Format(analysis); + + Assert.Contains("[Warning]", text); + Assert.Contains("No Join Predicate", text); + Assert.Contains("often misleading", text); + } } diff --git a/tests/PlanViewer.Core.Tests/Plans/missing-join-predicate.sqlplan b/tests/PlanViewer.Core.Tests/Plans/missing-join-predicate.sqlplan new file mode 100644 index 0000000000000000000000000000000000000000..222abb096a06208d2c2e528712f7f5f6873cee79 GIT binary patch literal 20422 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 Wq;a5*6W literal 0 HcmV?d00001