using System.Text; using Strata.SqlTools.Breakdowns.SqlServer; namespace Strata.SqlTools.Markdown.SqlServer; /// /// Generates Markdown documentation from QueryBreakdownCollection objects. /// Creates comprehensive reports including collection summaries, parameter analysis, and batch flow visualization. /// public static class QueryBreakdownCollectionGenerator { /// /// Generates a comprehensive collection report in Markdown format. /// /// The QueryBreakdownCollection to document. /// Optional title for the report. /// A string containing the Markdown documentation. 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(); } /// /// Generates a summary section for the collection. /// /// The QueryBreakdownCollection to summarize. /// Markdown summary section. 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(); } /// /// Generates a parameter analysis report. /// /// The QueryBreakdownCollection to analyze. /// Markdown parameter analysis section. 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(); } /// /// Generates a Mermaid diagram showing parameter dependencies across queries. /// /// The QueryBreakdownCollection to visualize. /// Mermaid diagram markdown. 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(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(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(); } /// /// Generates a detailed query composition report. /// /// The QueryBreakdownCollection to report on. /// Markdown composition report section. 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(); } /// /// Appends the query composition table for a single query. /// 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(); } /// /// Appends parameter information for a query. /// private static void AppendQueryParameters(StringBuilder sb, QueryBreakdown query) { // Collect all unique parameters from both ParameterList and Parameters dictionary var allParams = new Dictionary(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(); } /// /// Appends CTE section for a query. /// 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(); } /// /// Generates a batch execution flow diagram. /// /// The QueryBreakdownCollection to visualize. /// Whether to show transaction wrapping. /// Mermaid diagram markdown. 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(); } /// /// Gets the parameter type name from a parameter value. /// 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" }; } /// /// Escapes special Markdown characters. /// private static string EscapeMarkdown(string text) { return text .Replace("\\", "\\\\") .Replace("|", "\\|") .Replace("\n", "\\n"); } /// /// Formats clause presence as Yes/No with checkmark/cross. /// private static string FormatClausePresence(bool isPresent) => isPresent ? "✓ Yes" : "✗ No"; /// /// Determines the query type from a QueryBreakdown. /// 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"; } }