using System.Text; using Strata.SqlTools.Breakdowns.SqlServer; using Strata.SqlTools.SqlBreakdown.Interfaces; namespace Strata.SqlTools.Markdown.SqlServer; /// /// Generates Mermaid diagram markdown from SQL QueryBreakdown objects. /// Creates flowchart visualizations showing the query structure and flow. /// public class QueryBreakdownGenerator { /// /// Generates a Mermaid flowchart diagram from a QueryBreakdown. /// /// The QueryBreakdown to visualize. /// Optional title for the diagram. /// A string containing the Mermaid markdown diagram. public static string GenerateMermaidDiagram(QueryBreakdown queryBreakdown, string? title = null) { var sb = new StringBuilder(); // Add title if provided if (!string.IsNullOrWhiteSpace(title)) { sb.AppendLine($"### {title}"); sb.AppendLine(); } // Start Mermaid flowchart sb.AppendLine("```mermaid"); sb.AppendLine("flowchart TD"); sb.AppendLine(); int nodeId = 1; // Start node sb.AppendLine($" Start([Query Start]) --> Node{nodeId}"); sb.AppendLine(); // WITH clause (CTE) if (queryBreakdown.IsUsingWithClause) { sb.AppendLine($" Node{nodeId}[\"WITH Clause
Common Table Expressions\"]"); foreach (var withClause in queryBreakdown.WithClauses) { sb.AppendLine($" Node{nodeId} --> CTE{nodeId}[\"{EscapeMermaidText(withClause.TableName)}\"]"); nodeId++; } sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // SELECT clause if (!string.IsNullOrEmpty(queryBreakdown.SelectClause.Clause)) { var selectText = TruncateText(queryBreakdown.SelectClause.Clause, 50); sb.AppendLine($" Node{nodeId}[\"SELECT
{EscapeMermaidText(selectText)}\"]"); sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // FROM clause if (queryBreakdown.IsUsingFromClause && !string.IsNullOrWhiteSpace(queryBreakdown.FromClause?.Clause)) { var fromText = TruncateText(queryBreakdown.FromClause.Clause, 50); sb.AppendLine($" Node{nodeId}[\"FROM
{EscapeMermaidText(fromText)}\"]"); sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // WHERE clause if (queryBreakdown.IsUsingWhereClause && !string.IsNullOrWhiteSpace(queryBreakdown.WhereClause?.Clause)) { var whereText = TruncateText(queryBreakdown.WhereClause.Clause, 50); sb.AppendLine($" Node{nodeId}{{\"WHERE
{EscapeMermaidText(whereText)}\"}}"); sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // GROUP BY clause if (queryBreakdown.IsUsingGroupByClause && !string.IsNullOrWhiteSpace(queryBreakdown.GroupByClause?.Clause)) { var groupByText = TruncateText(queryBreakdown.GroupByClause.Clause, 50); sb.AppendLine($" Node{nodeId}[\"GROUP BY
{EscapeMermaidText(groupByText)}\"]"); sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // HAVING clause if (queryBreakdown.IsUsingHavingClause && !string.IsNullOrWhiteSpace(queryBreakdown.HavingClause?.Clause)) { var havingText = TruncateText(queryBreakdown.HavingClause.Clause, 50); sb.AppendLine($" Node{nodeId}{{\"HAVING
{EscapeMermaidText(havingText)}\"}}"); sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // ORDER BY clause if (queryBreakdown.IsUsingOrderByClause && !string.IsNullOrWhiteSpace(queryBreakdown.OrderByClause?.Clause)) { var orderByText = TruncateText(queryBreakdown.OrderByClause.Clause, 50); sb.AppendLine($" Node{nodeId}[\"ORDER BY
{EscapeMermaidText(orderByText)}\"]"); sb.AppendLine($" Node{nodeId - 1} --> Node{nodeId}"); sb.AppendLine(); nodeId++; } // End node sb.AppendLine($" Node{nodeId - 1} --> End([Query End])"); // End Mermaid diagram sb.AppendLine("```"); return sb.ToString(); } /// /// Generates a Mermaid flowchart diagram from any SQL breakdown implementing ISqlBreakdown. /// /// The SQL breakdown to visualize. /// Optional title for the diagram. /// A string containing the Mermaid markdown diagram. public static string GenerateMermaidDiagram(ISqlBreakdown sqlBreakdown, string? title = null) { // If it's a QueryBreakdown, use the specialized method if (sqlBreakdown is QueryBreakdown qb) { return GenerateMermaidDiagram(qb, title); } // For other SQL breakdowns, generate a simple diagram var sb = new StringBuilder(); if (!string.IsNullOrWhiteSpace(title)) { sb.AppendLine($"### {title}"); sb.AppendLine(); } sb.AppendLine("```mermaid"); sb.AppendLine("flowchart TD"); sb.AppendLine(); sb.AppendLine(" Start([SQL Statement Start])"); if (sqlBreakdown.IsUsingSetupClause) { sb.AppendLine(" Start --> Setup[\"Setup Clauses\"]"); sb.AppendLine(" Setup --> Main[\"Main Statement\"]"); } else { sb.AppendLine(" Start --> Main[\"Main Statement\"]"); } if (sqlBreakdown.IsUsingFinishClause) { sb.AppendLine(" Main --> Finish[\"Finish Clauses\"]"); sb.AppendLine(" Finish --> End([SQL Statement End])"); } else { sb.AppendLine(" Main --> End([SQL Statement End])"); } sb.AppendLine("```"); return sb.ToString(); } /// /// Escapes text for Mermaid diagram labels to prevent syntax errors. /// private static string EscapeMermaidText(string text) { return text .Replace("\"", """) .Replace("[", "[") .Replace("]", "]") .Replace("{", "{") .Replace("}", "}") .Replace("(", "(") .Replace(")", ")") .Replace("<", "<") .Replace(">", ">"); } /// /// Truncates text to a maximum length and adds ellipsis if needed. /// private static string TruncateText(string text, int maxLength) { if (string.IsNullOrWhiteSpace(text) || text.Length <= maxLength) { return text; } return string.Concat(text.AsSpan(0, maxLength), "..."); } }