Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion Dashboard/Controls/PlanViewerControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,27 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1)
iconRow.Children.Add(parBadge);
}

// Nonclustered index count badge (modification operators maintaining multiple NC indexes)
if (node.NonClusteredIndexCount > 0)
{
var ncBadge = new Border
{
Background = new SolidColorBrush(Color.FromRgb(0x6C, 0x75, 0x7D)),
CornerRadius = new CornerRadius(4),
Padding = new Thickness(4, 1, 4, 1),
Margin = new Thickness(4, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
Child = new TextBlock
{
Text = $"+{node.NonClusteredIndexCount} NC",
FontSize = 10,
FontWeight = FontWeights.SemiBold,
Foreground = Brushes.White
}
};
iconRow.Children.Add(ncBadge);
}

stack.Children.Add(iconRow);

// Operator name — use full name, let TextTrimming handle overflow
Expand Down Expand Up @@ -693,7 +714,7 @@ private void ShowPropertiesPanel(PlanNode node)
|| node.SortDistinct || node.StartupExpression
|| node.NLOptimized || node.WithOrderedPrefetch || node.WithUnorderedPrefetch
|| node.WithTies || node.Remoting || node.LocalParallelism
|| node.SpoolStack || node.DMLRequestSort
|| node.SpoolStack || node.DMLRequestSort || node.NonClusteredIndexCount > 0
|| !string.IsNullOrEmpty(node.OffsetExpression) || node.TopRows > 0
|| !string.IsNullOrEmpty(node.ConstantScanValues)
|| !string.IsNullOrEmpty(node.UdxUsedColumns);
Expand Down Expand Up @@ -742,6 +763,12 @@ private void ShowPropertiesPanel(PlanNode node)
AddPropertyRow("Primary Node Id", $"{node.PrimaryNodeId}");
if (node.DMLRequestSort)
AddPropertyRow("DML Request Sort", "True");
if (node.NonClusteredIndexCount > 0)
{
AddPropertyRow("NC Indexes Maintained", $"{node.NonClusteredIndexCount}");
foreach (var ixName in node.NonClusteredIndexNames)
AddPropertyRow("", ixName, isCode: true);
}
if (!string.IsNullOrEmpty(node.ActionColumn))
AddPropertyRow("Action Column", node.ActionColumn, isCode: true);
if (!string.IsNullOrEmpty(node.SegmentColumn))
Expand Down Expand Up @@ -1652,6 +1679,10 @@ private ToolTip BuildNodeTooltip(PlanNode node, List<PlanWarning>? allWarnings =
AddTooltipRow(stack, "Scan Direction", node.ScanDirection);
}

// NC index maintenance count
if (node.NonClusteredIndexCount > 0)
AddTooltipRow(stack, "NC Indexes Maintained", string.Join(", ", node.NonClusteredIndexNames));

// Operator details (key items only in tooltip)
var hasTooltipDetails = !string.IsNullOrEmpty(node.OrderBy)
|| !string.IsNullOrEmpty(node.TopExpression)
Expand Down
4 changes: 4 additions & 0 deletions Dashboard/Models/PlanModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ public class PlanNode
public List<PlanWarning> Warnings { get; set; } = new();
public bool HasWarnings => Warnings.Count > 0;

// Modification operator: nonclustered indexes maintained
public int NonClusteredIndexCount { get; set; }
public List<string> NonClusteredIndexNames { get; set; } = new();

// Tree structure
public List<PlanNode> Children { get; set; } = new();
public PlanNode? Parent { get; set; }
Expand Down
16 changes: 16 additions & 0 deletions Dashboard/Services/ShowPlanParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,22 @@ private static PlanNode ParseRelOp(XElement relOpEl)
if (actionColEl != null)
node.ActionColumn = FormatColumnRef(actionColEl);

// Nonclustered indexes maintained by modification operators (Update/SimpleUpdate/CreateIndex)
var opName = physicalOpEl.Name.LocalName;
if (opName is "Update" or "SimpleUpdate" or "CreateIndex")
{
var ncObjects = ScopedDescendants(physicalOpEl, Ns + "Object")
.Where(o => string.Equals(o.Attribute("IndexKind")?.Value, "NonClustered", StringComparison.OrdinalIgnoreCase))
.ToList();
node.NonClusteredIndexCount = ncObjects.Count;
foreach (var ncObj in ncObjects)
{
var ixName = ncObj.Attribute("Index")?.Value?.Replace("[", "").Replace("]", "");
if (!string.IsNullOrEmpty(ixName))
node.NonClusteredIndexNames.Add(ixName);
}
}

// SET predicate (UPDATE operator)
var setPredicateEl = physicalOpEl.Element(Ns + "SetPredicate");
if (setPredicateEl != null)
Expand Down
33 changes: 32 additions & 1 deletion Lite/Controls/PlanViewerControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,27 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1)
iconRow.Children.Add(parBadge);
}

// Nonclustered index count badge (modification operators maintaining multiple NC indexes)
if (node.NonClusteredIndexCount > 0)
{
var ncBadge = new Border
{
Background = new SolidColorBrush(Color.FromRgb(0x6C, 0x75, 0x7D)),
CornerRadius = new CornerRadius(4),
Padding = new Thickness(4, 1, 4, 1),
Margin = new Thickness(4, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
Child = new TextBlock
{
Text = $"+{node.NonClusteredIndexCount} NC",
FontSize = 10,
FontWeight = FontWeights.SemiBold,
Foreground = Brushes.White
}
};
iconRow.Children.Add(ncBadge);
}

stack.Children.Add(iconRow);

// Operator name — use full name, let TextTrimming handle overflow
Expand Down Expand Up @@ -710,7 +731,7 @@ private void ShowPropertiesPanel(PlanNode node)
|| node.SortDistinct || node.StartupExpression
|| node.NLOptimized || node.WithOrderedPrefetch || node.WithUnorderedPrefetch
|| node.WithTies || node.Remoting || node.LocalParallelism
|| node.SpoolStack || node.DMLRequestSort
|| node.SpoolStack || node.DMLRequestSort || node.NonClusteredIndexCount > 0
|| !string.IsNullOrEmpty(node.OffsetExpression) || node.TopRows > 0
|| !string.IsNullOrEmpty(node.ConstantScanValues)
|| !string.IsNullOrEmpty(node.UdxUsedColumns);
Expand Down Expand Up @@ -759,6 +780,12 @@ private void ShowPropertiesPanel(PlanNode node)
AddPropertyRow("Primary Node Id", $"{node.PrimaryNodeId}");
if (node.DMLRequestSort)
AddPropertyRow("DML Request Sort", "True");
if (node.NonClusteredIndexCount > 0)
{
AddPropertyRow("NC Indexes Maintained", $"{node.NonClusteredIndexCount}");
foreach (var ixName in node.NonClusteredIndexNames)
AddPropertyRow("", ixName, isCode: true);
}
if (!string.IsNullOrEmpty(node.ActionColumn))
AddPropertyRow("Action Column", node.ActionColumn, isCode: true);
if (!string.IsNullOrEmpty(node.SegmentColumn))
Expand Down Expand Up @@ -1664,6 +1691,10 @@ private ToolTip BuildNodeTooltip(PlanNode node, List<PlanWarning>? allWarnings =
AddTooltipRow(stack, "Scan Direction", node.ScanDirection);
}

// NC index maintenance count
if (node.NonClusteredIndexCount > 0)
AddTooltipRow(stack, "NC Indexes Maintained", string.Join(", ", node.NonClusteredIndexNames));

// Operator details (key items only in tooltip)
var hasTooltipDetails = !string.IsNullOrEmpty(node.OrderBy)
|| !string.IsNullOrEmpty(node.TopExpression)
Expand Down
4 changes: 4 additions & 0 deletions Lite/Models/PlanModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ public class PlanNode
public List<PlanWarning> Warnings { get; set; } = new();
public bool HasWarnings => Warnings.Count > 0;

// Modification operator: nonclustered indexes maintained
public int NonClusteredIndexCount { get; set; }
public List<string> NonClusteredIndexNames { get; set; } = new();

// Tree structure
public List<PlanNode> Children { get; set; } = new();
public PlanNode? Parent { get; set; }
Expand Down
16 changes: 16 additions & 0 deletions Lite/Services/ShowPlanParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,22 @@ private static PlanNode ParseRelOp(XElement relOpEl)
if (actionColEl != null)
node.ActionColumn = FormatColumnRef(actionColEl);

// Nonclustered indexes maintained by modification operators (Update/SimpleUpdate/CreateIndex)
var opName = physicalOpEl.Name.LocalName;
if (opName is "Update" or "SimpleUpdate" or "CreateIndex")
{
var ncObjects = ScopedDescendants(physicalOpEl, Ns + "Object")
.Where(o => string.Equals(o.Attribute("IndexKind")?.Value, "NonClustered", StringComparison.OrdinalIgnoreCase))
.ToList();
node.NonClusteredIndexCount = ncObjects.Count;
foreach (var ncObj in ncObjects)
{
var ixName = ncObj.Attribute("Index")?.Value?.Replace("[", "").Replace("]", "");
if (!string.IsNullOrEmpty(ixName))
node.NonClusteredIndexNames.Add(ixName);
}
}

// SET predicate (UPDATE operator)
var setPredicateEl = physicalOpEl.Element(Ns + "SetPredicate");
if (setPredicateEl != null)
Expand Down
Loading