chore: initial git load of code space

This commit is contained in:
Thom Lamb
2026-05-12 08:52:33 -05:00
parent 9abada692f
commit 5e467bcc9c
384 changed files with 65960 additions and 2 deletions
@@ -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";
}
}