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 0000000..222abb0 Binary files /dev/null and b/tests/PlanViewer.Core.Tests/Plans/missing-join-predicate.sqlplan differ