chore: initial git load of code space
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||
|
||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Generates Markdown documentation from QueryBreakdownCollection objects.
|
||||
/// Creates comprehensive reports including collection summaries, parameter analysis, and batch flow visualization.
|
||||
/// </summary>
|
||||
public static class QueryBreakdownCollectionGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a comprehensive collection report in Markdown format.
|
||||
/// </summary>
|
||||
/// <param name="collection">The QueryBreakdownCollection to document.</param>
|
||||
/// <param name="title">Optional title for the report.</param>
|
||||
/// <returns>A string containing the Markdown documentation.</returns>
|
||||
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
sb.AppendLine($"# {title}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// Collection Summary
|
||||
sb.Append(GenerateCollectionSummary(collection));
|
||||
sb.AppendLine();
|
||||
|
||||
// Parameter Analysis
|
||||
sb.Append(GenerateParameterAnalysis(collection));
|
||||
sb.AppendLine();
|
||||
|
||||
// Query Composition Report
|
||||
sb.Append(GenerateQueryCompositionReport(collection));
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a summary section for the collection.
|
||||
/// </summary>
|
||||
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
||||
/// <returns>Markdown summary section.</returns>
|
||||
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("## Collection Summary");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("| Metric | Value |");
|
||||
sb.AppendLine("|--------|-------|");
|
||||
sb.AppendLine($"| Total Queries | {collection.QueryBreakdowns.Count} |");
|
||||
sb.AppendLine($"| Total Parameters | {collection.GetAllUniqueParameters().Count()} |");
|
||||
sb.AppendLine($"| Total Columns Selected | {collection.GetTotalSelectedColumns()} |");
|
||||
sb.AppendLine($"| Unique Tables | {collection.GetUniqueTableReferences().Count()} |");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a parameter analysis report.
|
||||
/// </summary>
|
||||
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
||||
/// <returns>Markdown parameter analysis section.</returns>
|
||||
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var paramReport = collection.GetParameterUsageReport().ToList();
|
||||
|
||||
sb.AppendLine("## Parameter Analysis");
|
||||
sb.AppendLine();
|
||||
|
||||
if (paramReport.Count == 0)
|
||||
{
|
||||
sb.AppendLine("### Parameters");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("No parameters are used in this collection.");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
sb.AppendLine("### Parameters");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("| Parameter | Type | Used In | Value |");
|
||||
sb.AppendLine("|-----------|------|---------|-------|");
|
||||
|
||||
foreach (var param in paramReport.OrderBy(p => p.ParameterName))
|
||||
{
|
||||
var usageIndicator = param.IsUsedInAllQueries ? "✓ All" : $"{param.UsedInQueryCount}/{param.TotalQueries}";
|
||||
var value = param.Value?.ToString() ?? "NULL";
|
||||
|
||||
sb.AppendLine($"| @{param.ParameterName} | {GetParameterType(param.Value)} | {usageIndicator} | `{EscapeMarkdown(value)}` |");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("### Parameter Dependency Diagram");
|
||||
sb.AppendLine();
|
||||
sb.Append(GenerateParameterDependencyDiagram(collection));
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
||||
/// </summary>
|
||||
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
||||
/// <returns>Mermaid diagram markdown.</returns>
|
||||
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("```mermaid");
|
||||
sb.AppendLine("graph TD");
|
||||
sb.AppendLine();
|
||||
|
||||
var queryBreakdowns = collection.QueryBreakdowns;
|
||||
|
||||
// Collect all unique parameter names from both ParameterList and Parameters dictionary
|
||||
var allParamNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var query in queryBreakdowns)
|
||||
{
|
||||
foreach (var param in query.ParameterList)
|
||||
{
|
||||
allParamNames.Add(param.Name);
|
||||
}
|
||||
foreach (var paramName in query.Parameters.Keys)
|
||||
{
|
||||
allParamNames.Add(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
var parameters = allParamNames.OrderBy(p => p).ToList();
|
||||
|
||||
// Create parameter nodes
|
||||
for (int i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
var paramNode = $"param{i}";
|
||||
sb.AppendLine($" {paramNode}[\"@{parameters[i]}\"]");
|
||||
sb.AppendLine($" style {paramNode} fill:#e1f5ff");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
// Create query nodes and connections
|
||||
for (int i = 0; i < queryBreakdowns.Count; i++)
|
||||
{
|
||||
var query = queryBreakdowns[i];
|
||||
var queryNode = $"query{i}";
|
||||
var queryType = DetermineQueryType(query);
|
||||
|
||||
sb.AppendLine($" {queryNode}[\"Query #{i}: {queryType}\"]");
|
||||
sb.AppendLine($" style {queryNode} fill:#f3e5f5");
|
||||
|
||||
// Collect all parameter names used by this query
|
||||
var queryParamNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Add from ParameterList (parsed parameters)
|
||||
foreach (var param in query.ParameterList)
|
||||
{
|
||||
queryParamNames.Add(param.Name);
|
||||
}
|
||||
|
||||
// Add from Parameters dictionary (manually added parameters)
|
||||
foreach (var paramName in query.Parameters.Keys)
|
||||
{
|
||||
queryParamNames.Add(paramName);
|
||||
}
|
||||
|
||||
// Connect parameters to this query
|
||||
foreach (var paramName in queryParamNames)
|
||||
{
|
||||
var paramIndex = parameters.FindIndex(p => p.Equals(paramName, StringComparison.OrdinalIgnoreCase));
|
||||
if (paramIndex >= 0)
|
||||
{
|
||||
var paramNode = $"param{paramIndex}";
|
||||
sb.AppendLine($" {paramNode} --> {queryNode}");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a detailed query composition report.
|
||||
/// </summary>
|
||||
/// <param name="collection">The QueryBreakdownCollection to report on.</param>
|
||||
/// <returns>Markdown composition report section.</returns>
|
||||
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("## Query Composition Report");
|
||||
sb.AppendLine();
|
||||
|
||||
var summaries = collection.GetQuerySummaries().ToList();
|
||||
|
||||
for (int i = 0; i < summaries.Count; i++)
|
||||
{
|
||||
var summary = summaries[i];
|
||||
var query = collection.QueryBreakdowns[i];
|
||||
|
||||
AppendQueryCompositionTable(sb, i, summary);
|
||||
AppendQueryParameters(sb, query);
|
||||
AppendQueryCteSections(sb, summary, query);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the query composition table for a single query.
|
||||
/// </summary>
|
||||
private static void AppendQueryCompositionTable(StringBuilder sb, int queryIndex, QuerySummary summary)
|
||||
{
|
||||
sb.AppendLine($"### Query #{queryIndex}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("| Aspect | Present |");
|
||||
sb.AppendLine("|--------|---------|");
|
||||
sb.AppendLine($"| SELECT Clause | {FormatClausePresence(summary.HasSelectClause)} |");
|
||||
sb.AppendLine($"| FROM Clause | {FormatClausePresence(summary.HasFromClause)} |");
|
||||
sb.AppendLine($"| WHERE Clause | {FormatClausePresence(summary.HasWhereClause)} |");
|
||||
sb.AppendLine($"| GROUP BY Clause | {FormatClausePresence(summary.HasGroupByClause)} |");
|
||||
sb.AppendLine($"| HAVING Clause | {FormatClausePresence(summary.HasHavingClause)} |");
|
||||
sb.AppendLine($"| ORDER BY Clause | {FormatClausePresence(summary.HasOrderByClause)} |");
|
||||
sb.AppendLine($"| CTE (WITH) | {FormatClausePresence(summary.HasCTE)} |");
|
||||
sb.AppendLine($"| Columns | {summary.ColumnCount} |");
|
||||
sb.AppendLine($"| Parameters | {summary.ParameterCount} |");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends parameter information for a query.
|
||||
/// </summary>
|
||||
private static void AppendQueryParameters(StringBuilder sb, QueryBreakdown query)
|
||||
{
|
||||
// Collect all unique parameters from both ParameterList and Parameters dictionary
|
||||
var allParams = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Add from ParameterList (parsed parameters)
|
||||
foreach (var param in query.ParameterList)
|
||||
{
|
||||
allParams[param.Name] = param.Value;
|
||||
}
|
||||
|
||||
// Add from Parameters dictionary (manually added parameters)
|
||||
foreach (var param in query.Parameters)
|
||||
{
|
||||
allParams[param.Key] = param.Value;
|
||||
}
|
||||
|
||||
if (allParams.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sb.AppendLine("**Parameters Used:**");
|
||||
sb.AppendLine();
|
||||
foreach (var paramName in allParams.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var value = allParams[paramName];
|
||||
sb.AppendLine($"- `@{paramName}` = `{value?.ToString() ?? "NULL"}`");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends CTE section for a query.
|
||||
/// </summary>
|
||||
private static void AppendQueryCteSections(StringBuilder sb, QuerySummary summary, QueryBreakdown query)
|
||||
{
|
||||
if (!summary.HasCTE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sb.AppendLine("**CTEs Defined:**");
|
||||
sb.AppendLine();
|
||||
foreach (var cte in query.WithClauses)
|
||||
{
|
||||
sb.AppendLine($"- `{cte.TableName}`");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a batch execution flow diagram.
|
||||
/// </summary>
|
||||
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
||||
/// <param name="includeTransaction">Whether to show transaction wrapping.</param>
|
||||
/// <returns>Mermaid diagram markdown.</returns>
|
||||
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("```mermaid");
|
||||
sb.AppendLine("flowchart TD");
|
||||
sb.AppendLine();
|
||||
|
||||
int nodeId = 0;
|
||||
|
||||
// Handle empty collection
|
||||
if (collection.QueryBreakdowns.Count == 0)
|
||||
{
|
||||
if (includeTransaction)
|
||||
{
|
||||
sb.AppendLine($" Start([Batch Start]) --> node0[\"BEGIN TRANSACTION\"]");
|
||||
sb.AppendLine($" node0 --> node1[\"COMMIT TRANSACTION\"]");
|
||||
sb.AppendLine($" node1 --> End([Batch Complete])");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($" Start([Batch Start]) --> End([Batch Complete])");
|
||||
}
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine();
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Start node
|
||||
if (includeTransaction)
|
||||
{
|
||||
sb.AppendLine($" node{nodeId}[\"BEGIN TRANSACTION\"]");
|
||||
sb.AppendLine($" Start([Batch Start]) --> node{nodeId}");
|
||||
nodeId++;
|
||||
sb.AppendLine($" node{nodeId - 1} --> node{nodeId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($" Start([Batch Start]) --> node{nodeId}");
|
||||
}
|
||||
|
||||
// Query nodes
|
||||
for (int i = 0; i < collection.QueryBreakdowns.Count; i++)
|
||||
{
|
||||
if (i < collection.QueryBreakdowns.Count - 1)
|
||||
{
|
||||
// Not the last query - connect to next
|
||||
sb.AppendLine($" node{nodeId}[\"Query {i}\"] --> node{nodeId + 1}");
|
||||
nodeId++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last query - connect to End (or COMMIT if transaction)
|
||||
if (includeTransaction)
|
||||
{
|
||||
sb.AppendLine($" node{nodeId}[\"Query {i}\"] --> node{nodeId + 1}");
|
||||
nodeId++;
|
||||
sb.AppendLine($" node{nodeId}[\"COMMIT TRANSACTION\"]");
|
||||
sb.AppendLine($" node{nodeId} --> End([Batch Complete])");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($" node{nodeId}[\"Query {i}\"] --> End([Batch Complete])");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter type name from a parameter value.
|
||||
/// </summary>
|
||||
private static string GetParameterType(object? value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
null => "NULL",
|
||||
bool => "BIT",
|
||||
byte => "TINYINT",
|
||||
short => "SMALLINT",
|
||||
int => "INT",
|
||||
long => "BIGINT",
|
||||
float => "REAL",
|
||||
double => "FLOAT",
|
||||
decimal => "DECIMAL",
|
||||
string => "NVARCHAR",
|
||||
DateTime => "DATETIME2",
|
||||
_ => "VARIANT"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes special Markdown characters.
|
||||
/// </summary>
|
||||
private static string EscapeMarkdown(string text)
|
||||
{
|
||||
return text
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("|", "\\|")
|
||||
.Replace("\n", "\\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats clause presence as Yes/No with checkmark/cross.
|
||||
/// </summary>
|
||||
private static string FormatClausePresence(bool isPresent)
|
||||
=> isPresent ? "✓ Yes" : "✗ No";
|
||||
|
||||
/// <summary>
|
||||
/// Determines the query type from a QueryBreakdown.
|
||||
/// </summary>
|
||||
private static string DetermineQueryType(QueryBreakdown query)
|
||||
{
|
||||
var hasSelect = !string.IsNullOrWhiteSpace(query.SelectClause?.Clause);
|
||||
if (hasSelect)
|
||||
{
|
||||
return "SELECT";
|
||||
}
|
||||
|
||||
var hasFrom = !string.IsNullOrWhiteSpace(query.FromClause?.Clause);
|
||||
return hasFrom ? "FROM" : "QUERY";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
|
||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Generates Mermaid diagram markdown from SQL QueryBreakdown objects.
|
||||
/// Creates flowchart visualizations showing the query structure and flow.
|
||||
/// </summary>
|
||||
public class QueryBreakdownGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a Mermaid flowchart diagram from a QueryBreakdown.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The QueryBreakdown to visualize.</param>
|
||||
/// <param name="title">Optional title for the diagram.</param>
|
||||
/// <returns>A string containing the Mermaid markdown diagram.</returns>
|
||||
public 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<br/>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<br/>{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<br/>{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<br/>{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<br/>{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<br/>{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<br/>{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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Mermaid flowchart diagram from any SQL breakdown implementing ISqlBreakdown.
|
||||
/// </summary>
|
||||
/// <param name="sqlBreakdown">The SQL breakdown to visualize.</param>
|
||||
/// <param name="title">Optional title for the diagram.</param>
|
||||
/// <returns>A string containing the Mermaid markdown diagram.</returns>
|
||||
public 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes text for Mermaid diagram labels to prevent syntax errors.
|
||||
/// </summary>
|
||||
private string EscapeMermaidText(string text)
|
||||
{
|
||||
return text
|
||||
.Replace("\"", """)
|
||||
.Replace("[", "[")
|
||||
.Replace("]", "]")
|
||||
.Replace("{", "{")
|
||||
.Replace("}", "}")
|
||||
.Replace("(", "(")
|
||||
.Replace(")", ")")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates text to a maximum length and adds ellipsis if needed.
|
||||
/// </summary>
|
||||
private string TruncateText(string text, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text) || text.Length <= maxLength)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.Substring(0, maxLength) + "...";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
|
||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Generates Mermaid sequence diagrams from SQL statements to visualize statement execution flow.
|
||||
/// </summary>
|
||||
public class SqlStatementGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a Mermaid sequence diagram showing SQL statement execution.
|
||||
/// </summary>
|
||||
/// <param name="sqlBreakdown">The SQL breakdown to visualize.</param>
|
||||
/// <param name="title">Optional title for the diagram.</param>
|
||||
/// <returns>A string containing the Mermaid markdown sequence diagram.</returns>
|
||||
public string GenerateSequenceDiagram(ISqlBreakdown sqlBreakdown, string? title = null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
sb.AppendLine($"### {title}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("```mermaid");
|
||||
sb.AppendLine("sequenceDiagram");
|
||||
sb.AppendLine(" participant App as Application");
|
||||
sb.AppendLine(" participant DB as Database");
|
||||
sb.AppendLine();
|
||||
|
||||
// Setup clauses
|
||||
if (sqlBreakdown.IsUsingSetupClause)
|
||||
{
|
||||
sb.AppendLine(" App->>DB: Execute Setup Clauses");
|
||||
foreach (var setupClause in sqlBreakdown.SetupClauses)
|
||||
{
|
||||
var setupText = TruncateText(setupClause, 40);
|
||||
sb.AppendLine($" activate DB");
|
||||
sb.AppendLine($" Note right of DB: {EscapeMermaidText(setupText)}");
|
||||
sb.AppendLine($" DB-->>App: Setup Complete");
|
||||
sb.AppendLine($" deactivate DB");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// Main statement
|
||||
sb.AppendLine(" App->>DB: Execute Main Statement");
|
||||
sb.AppendLine(" activate DB");
|
||||
sb.AppendLine($" Note right of DB: Process SQL Statement");
|
||||
sb.AppendLine(" DB-->>App: Return Results");
|
||||
sb.AppendLine(" deactivate DB");
|
||||
sb.AppendLine();
|
||||
|
||||
// Finish clauses
|
||||
if (sqlBreakdown.IsUsingFinishClause)
|
||||
{
|
||||
sb.AppendLine(" App->>DB: Execute Finish Clauses");
|
||||
sb.AppendLine(" activate DB");
|
||||
sb.AppendLine($" Note right of DB: Cleanup Operations");
|
||||
sb.AppendLine(" DB-->>App: Cleanup Complete");
|
||||
sb.AppendLine(" deactivate DB");
|
||||
}
|
||||
|
||||
sb.AppendLine("```");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an entity-relationship diagram for tables referenced in the SQL statement.
|
||||
/// </summary>
|
||||
/// <param name="tableNames">List of table names referenced in the query.</param>
|
||||
/// <param name="title">Optional title for the diagram.</param>
|
||||
/// <returns>A string containing the Mermaid markdown ER diagram.</returns>
|
||||
public string GenerateEntityRelationshipDiagram(IEnumerable<string> tableNames, string? title = null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
sb.AppendLine($"### {title}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("```mermaid");
|
||||
sb.AppendLine("erDiagram");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var tableName in tableNames)
|
||||
{
|
||||
var cleanName = CleanTableName(tableName);
|
||||
sb.AppendLine($" {cleanName} {{");
|
||||
sb.AppendLine($" string columns \"Referenced in query\"");
|
||||
sb.AppendLine($" }}");
|
||||
}
|
||||
|
||||
sb.AppendLine("```");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes text for Mermaid diagram labels.
|
||||
/// </summary>
|
||||
private string EscapeMermaidText(string text)
|
||||
{
|
||||
return text
|
||||
.Replace("\"", """)
|
||||
.Replace("\n", " ")
|
||||
.Replace("\r", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates text to a maximum length.
|
||||
/// </summary>
|
||||
private string TruncateText(string text, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text) || text.Length <= maxLength)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.Substring(0, maxLength) + "...";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans table name for use in Mermaid diagrams.
|
||||
/// </summary>
|
||||
private string CleanTableName(string tableName)
|
||||
{
|
||||
return tableName
|
||||
.Replace("[", "")
|
||||
.Replace("]", "")
|
||||
.Replace(".", "_")
|
||||
.Replace(" ", "_");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user