Merge pull request 'chore: working on cleaning up more dup lines' (#9) from fix/sonarqube-duplicate-code-lines-2 into main
SonarQube Analysis / sonarqube (push) Successful in 3m50s
SonarQube Analysis / sonarqube (push) Successful in 3m50s
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
@@ -0,0 +1,456 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||||
|
|
||||||
|
namespace Strata.SqlTools.Markdown.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the dialect-specific formatting choices used when rendering a collection report.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The structural Markdown is identical across dialects; only these hooks differ (parameter syntax,
|
||||||
|
/// data-type names, and Mermaid node colors), so the per-dialect generators supply an instance of this
|
||||||
|
/// type and delegate the heavy lifting to <see cref="CollectionReportWriter"/>.
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class MarkdownDialectFormat
|
||||||
|
{
|
||||||
|
/// <summary>Formats a parameter name as it appears in tables and bullet lists (e.g. <c>@id</c>).</summary>
|
||||||
|
public required Func<string, string> ParameterTableLabel { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Formats a parameter name as it appears inside a Mermaid node (e.g. <c>:id</c>).</summary>
|
||||||
|
public required Func<string, string> ParameterNodeLabel { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Maps a parameter value to the dialect's data-type name.</summary>
|
||||||
|
public required Func<object?, string> ParameterTypeName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Mermaid fill color for parameter nodes.</summary>
|
||||||
|
public required string ParameterNodeFill { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Mermaid fill color for query nodes.</summary>
|
||||||
|
public required string QueryNodeFill { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Whether the query-composition table includes a HAVING clause row.</summary>
|
||||||
|
public required bool IncludeHavingRow { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Carries the precomputed usage statistics for a single parameter, decoupling the writer from each
|
||||||
|
/// dialect's concrete parameter-usage report type.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ParameterUsageRow
|
||||||
|
{
|
||||||
|
/// <summary>The parameter name (without any dialect prefix).</summary>
|
||||||
|
public required string ParameterName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Whether the parameter is used by every query in the collection.</summary>
|
||||||
|
public required bool IsUsedInAllQueries { get; init; }
|
||||||
|
|
||||||
|
/// <summary>The number of queries that use the parameter.</summary>
|
||||||
|
public required int UsedInQueryCount { get; init; }
|
||||||
|
|
||||||
|
/// <summary>The total number of queries in the collection.</summary>
|
||||||
|
public required int TotalQueries { get; init; }
|
||||||
|
|
||||||
|
/// <summary>The parameter's last-seen value.</summary>
|
||||||
|
public required object? Value { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the dialect-agnostic Markdown for query breakdown collection reports. The per-dialect
|
||||||
|
/// generators supply a <see cref="MarkdownDialectFormat"/> and precomputed data; this writer produces
|
||||||
|
/// the identical structural Markdown that previously lived in each dialect generator.
|
||||||
|
/// </summary>
|
||||||
|
internal static class CollectionReportWriter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Assembles a full collection report from a title and the already-rendered sections, separating
|
||||||
|
/// sections with a blank line.
|
||||||
|
/// </summary>
|
||||||
|
public static string CollectionReport(string? title, IEnumerable<string> sections)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(title))
|
||||||
|
{
|
||||||
|
sb.AppendLine($"# {title}");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = true;
|
||||||
|
foreach (var section in sections)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(section);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the collection summary table.
|
||||||
|
/// </summary>
|
||||||
|
public static string CollectionSummary(int queryCount, int uniqueParameterCount, int totalSelectedColumns, int uniqueTableCount)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("## Collection Summary");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine("| Metric | Value |");
|
||||||
|
sb.AppendLine("|--------|-------|");
|
||||||
|
sb.AppendLine($"| Total Queries | {queryCount} |");
|
||||||
|
sb.AppendLine($"| Total Parameters | {uniqueParameterCount} |");
|
||||||
|
sb.AppendLine($"| Total Columns Selected | {totalSelectedColumns} |");
|
||||||
|
sb.AppendLine($"| Unique Tables | {uniqueTableCount} |");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the parameter analysis section, including the dependency diagram.
|
||||||
|
/// </summary>
|
||||||
|
public static string ParameterAnalysis(
|
||||||
|
IReadOnlyList<ParameterUsageRow> parameters,
|
||||||
|
IEnumerable<QueryBreakdown> queries,
|
||||||
|
MarkdownDialectFormat format)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("## Parameter Analysis");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
if (parameters.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 parameters.OrderBy(p => p.ParameterName))
|
||||||
|
{
|
||||||
|
var usageIndicator = param.IsUsedInAllQueries ? "✓ All" : $"{param.UsedInQueryCount}/{param.TotalQueries}";
|
||||||
|
var value = param.Value?.ToString() ?? "NULL";
|
||||||
|
|
||||||
|
sb.AppendLine($"| {format.ParameterTableLabel(param.ParameterName)} | {format.ParameterTypeName(param.Value)} | {usageIndicator} | `{EscapeMarkdown(value)}` |");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("### Parameter Dependency Diagram");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.Append(ParameterDependencyDiagram(queries, format));
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the Mermaid diagram showing parameter dependencies across queries.
|
||||||
|
/// </summary>
|
||||||
|
public static string ParameterDependencyDiagram(IEnumerable<QueryBreakdown> queries, MarkdownDialectFormat format)
|
||||||
|
{
|
||||||
|
var queryBreakdowns = queries as IReadOnlyList<QueryBreakdown> ?? queries.ToList();
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("```mermaid");
|
||||||
|
sb.AppendLine("graph TD");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// 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}[\"{format.ParameterNodeLabel(parameters[i])}\"]");
|
||||||
|
sb.AppendLine($" style {paramNode} fill:{format.ParameterNodeFill}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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:{format.QueryNodeFill}");
|
||||||
|
|
||||||
|
// 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>
|
||||||
|
/// Renders the per-query composition report. The optional <paramref name="appendExtra"/> hook lets a
|
||||||
|
/// dialect append additional per-query sections (for example, Snowflake feature notes).
|
||||||
|
/// </summary>
|
||||||
|
public static string QueryCompositionReport<TQuery>(
|
||||||
|
IReadOnlyList<TQuery> queries,
|
||||||
|
MarkdownDialectFormat format,
|
||||||
|
Action<StringBuilder, TQuery>? appendExtra = null)
|
||||||
|
where TQuery : QueryBreakdown
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("## Query Composition Report");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
for (int i = 0; i < queries.Count; i++)
|
||||||
|
{
|
||||||
|
var query = queries[i];
|
||||||
|
|
||||||
|
AppendQueryCompositionTable(sb, i, query, format.IncludeHavingRow);
|
||||||
|
AppendQueryParameters(sb, query, format);
|
||||||
|
AppendQueryCteSections(sb, query);
|
||||||
|
appendExtra?.Invoke(sb, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a batch execution flow diagram. <paramref name="openLabel"/> and <paramref name="closeLabel"/>
|
||||||
|
/// are the optional opening/closing node labels (for example, transaction or session-setup statements);
|
||||||
|
/// pass null to omit either.
|
||||||
|
/// </summary>
|
||||||
|
public static string BatchFlowDiagram(int queryCount, string? openLabel, string? closeLabel)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("```mermaid");
|
||||||
|
sb.AppendLine("flowchart TD");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Handle empty collection
|
||||||
|
if (queryCount == 0)
|
||||||
|
{
|
||||||
|
if (openLabel != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" Start([Batch Start]) --> node0[\"{openLabel}\"]");
|
||||||
|
if (closeLabel != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" node0 --> node1[\"{closeLabel}\"]");
|
||||||
|
sb.AppendLine($" node1 --> End([Batch Complete])");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine($" node0 --> End([Batch Complete])");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine($" Start([Batch Start]) --> End([Batch Complete])");
|
||||||
|
}
|
||||||
|
sb.AppendLine("```");
|
||||||
|
sb.AppendLine();
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int nodeId = 0;
|
||||||
|
|
||||||
|
// Start node
|
||||||
|
if (openLabel != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" node{nodeId}[\"{openLabel}\"]");
|
||||||
|
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 < queryCount; i++)
|
||||||
|
{
|
||||||
|
if (i < queryCount - 1)
|
||||||
|
{
|
||||||
|
// Not the last query - connect to next
|
||||||
|
sb.AppendLine($" node{nodeId}[\"Query {i}\"] --> node{nodeId + 1}");
|
||||||
|
nodeId++;
|
||||||
|
}
|
||||||
|
else if (closeLabel != null)
|
||||||
|
{
|
||||||
|
// Last query - connect to the closing node
|
||||||
|
sb.AppendLine($" node{nodeId}[\"Query {i}\"] --> node{nodeId + 1}");
|
||||||
|
nodeId++;
|
||||||
|
sb.AppendLine($" node{nodeId}[\"{closeLabel}\"]");
|
||||||
|
sb.AppendLine($" node{nodeId} --> End([Batch Complete])");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Last query - connect straight to the end
|
||||||
|
sb.AppendLine($" node{nodeId}[\"Query {i}\"] --> End([Batch Complete])");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("```");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the query composition table for a single query.
|
||||||
|
/// </summary>
|
||||||
|
private static void AppendQueryCompositionTable(StringBuilder sb, int queryIndex, QueryBreakdown query, bool includeHavingRow)
|
||||||
|
{
|
||||||
|
var hasSelect = !string.IsNullOrWhiteSpace(query.SelectClause?.Clause);
|
||||||
|
|
||||||
|
sb.AppendLine($"### Query #{queryIndex}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("| Aspect | Present |");
|
||||||
|
sb.AppendLine("|--------|---------|");
|
||||||
|
sb.AppendLine($"| SELECT Clause | {FormatClausePresence(hasSelect)} |");
|
||||||
|
sb.AppendLine($"| FROM Clause | {FormatClausePresence(!string.IsNullOrWhiteSpace(query.FromClause?.Clause))} |");
|
||||||
|
sb.AppendLine($"| WHERE Clause | {FormatClausePresence(!string.IsNullOrWhiteSpace(query.WhereClause?.Clause))} |");
|
||||||
|
sb.AppendLine($"| GROUP BY Clause | {FormatClausePresence(!string.IsNullOrWhiteSpace(query.GroupByClause?.Clause))} |");
|
||||||
|
if (includeHavingRow)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"| HAVING Clause | {FormatClausePresence(!string.IsNullOrWhiteSpace(query.HavingClause?.Clause))} |");
|
||||||
|
}
|
||||||
|
sb.AppendLine($"| ORDER BY Clause | {FormatClausePresence(!string.IsNullOrWhiteSpace(query.OrderByClause?.Clause))} |");
|
||||||
|
sb.AppendLine($"| CTE (WITH) | {FormatClausePresence(query.WithClauses.Count > 0)} |");
|
||||||
|
sb.AppendLine($"| Columns | {(hasSelect ? query.SelectClause!.Clause!.Split(',').Length : 0)} |");
|
||||||
|
sb.AppendLine($"| Parameters | {query.ParameterList.Count()} |");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends parameter information for a query.
|
||||||
|
/// </summary>
|
||||||
|
private static void AppendQueryParameters(StringBuilder sb, QueryBreakdown query, MarkdownDialectFormat format)
|
||||||
|
{
|
||||||
|
// 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($"- `{format.ParameterNodeLabel(paramName)}` = `{value?.ToString() ?? "NULL"}`");
|
||||||
|
}
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends the CTE section for a query when it defines any.
|
||||||
|
/// </summary>
|
||||||
|
private static void AppendQueryCteSections(StringBuilder sb, QueryBreakdown query)
|
||||||
|
{
|
||||||
|
if (query.WithClauses.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("**CTEs Defined:**");
|
||||||
|
sb.AppendLine();
|
||||||
|
foreach (var cte in query.WithClauses)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"- `{cte.TableName}`");
|
||||||
|
}
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Text;
|
|
||||||
using Strata.SqlTools.Breakdowns.PostgreSql;
|
using Strata.SqlTools.Breakdowns.PostgreSql;
|
||||||
using QuerySummary = Strata.SqlTools.Breakdowns.SqlServer.QuerySummary;
|
using Strata.SqlTools.Markdown.Common;
|
||||||
|
|
||||||
namespace Strata.SqlTools.Markdown.PostgreSql;
|
namespace Strata.SqlTools.Markdown.PostgreSql;
|
||||||
|
|
||||||
@@ -11,6 +10,16 @@ namespace Strata.SqlTools.Markdown.PostgreSql;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class QueryBreakdownCollectionGenerator
|
public static class QueryBreakdownCollectionGenerator
|
||||||
{
|
{
|
||||||
|
private static readonly MarkdownDialectFormat Format = new()
|
||||||
|
{
|
||||||
|
ParameterTableLabel = FormatParameter,
|
||||||
|
ParameterNodeLabel = FormatParameter,
|
||||||
|
ParameterTypeName = GetParameterType,
|
||||||
|
ParameterNodeFill = "#e8f5e9",
|
||||||
|
QueryNodeFill = "#fff3e0",
|
||||||
|
IncludeHavingRow = true
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a comprehensive collection report in Markdown format with PostgreSQL-specific information.
|
/// Generates a comprehensive collection report in Markdown format with PostgreSQL-specific information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -18,28 +27,12 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="title">Optional title for the report.</param>
|
/// <param name="title">Optional title for the report.</param>
|
||||||
/// <returns>A string containing the Markdown documentation.</returns>
|
/// <returns>A string containing the Markdown documentation.</returns>
|
||||||
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
||||||
{
|
=> CollectionReportWriter.CollectionReport(title, new[]
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(title))
|
|
||||||
{
|
{
|
||||||
sb.AppendLine($"# {title}");
|
GenerateCollectionSummary(collection),
|
||||||
sb.AppendLine();
|
GenerateParameterAnalysis(collection),
|
||||||
}
|
GenerateQueryCompositionReport(collection)
|
||||||
|
});
|
||||||
// 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>
|
/// <summary>
|
||||||
/// Generates a summary section for the collection.
|
/// Generates a summary section for the collection.
|
||||||
@@ -47,20 +40,11 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
||||||
/// <returns>Markdown summary section.</returns>
|
/// <returns>Markdown summary section.</returns>
|
||||||
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.CollectionSummary(
|
||||||
var sb = new StringBuilder();
|
collection.QueryBreakdowns.Count,
|
||||||
sb.AppendLine("## Collection Summary");
|
collection.GetAllUniqueParameters().Count(),
|
||||||
sb.AppendLine();
|
collection.GetTotalSelectedColumns(),
|
||||||
|
collection.GetUniqueTableReferences().Count());
|
||||||
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>
|
/// <summary>
|
||||||
/// Generates a parameter analysis report with PostgreSQL parameter syntax support.
|
/// Generates a parameter analysis report with PostgreSQL parameter syntax support.
|
||||||
@@ -68,44 +52,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
||||||
/// <returns>Markdown parameter analysis section.</returns>
|
/// <returns>Markdown parameter analysis section.</returns>
|
||||||
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.ParameterAnalysis(MapParameters(collection), collection.QueryBreakdowns, Format);
|
||||||
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";
|
|
||||||
// PostgreSQL supports both $n positional and :named parameters
|
|
||||||
var paramSyntax = int.TryParse(param.ParameterName, out _)
|
|
||||||
? $"${param.ParameterName}"
|
|
||||||
: $":{param.ParameterName}";
|
|
||||||
sb.AppendLine($"| {paramSyntax} | {GetParameterType(param.Value)} | {usageIndicator} | `{EscapeMarkdown(value)}` |");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine("### Parameter Dependency Diagram");
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.Append(GenerateParameterDependencyDiagram(collection));
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
||||||
@@ -113,87 +60,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
||||||
/// <returns>Mermaid diagram markdown.</returns>
|
/// <returns>Mermaid diagram markdown.</returns>
|
||||||
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.ParameterDependencyDiagram(collection.QueryBreakdowns, Format);
|
||||||
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}";
|
|
||||||
var paramSyntax = int.TryParse(parameters[i], out _)
|
|
||||||
? $"${parameters[i]}"
|
|
||||||
: $":{parameters[i]}";
|
|
||||||
sb.AppendLine($" {paramNode}[\"{paramSyntax}\"]");
|
|
||||||
sb.AppendLine($" style {paramNode} fill:#e8f5e9");
|
|
||||||
}
|
|
||||||
|
|
||||||
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:#fff3e0");
|
|
||||||
|
|
||||||
// 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>
|
/// <summary>
|
||||||
/// Generates a detailed query composition report.
|
/// Generates a detailed query composition report.
|
||||||
@@ -201,103 +68,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to report on.</param>
|
/// <param name="collection">The QueryBreakdownCollection to report on.</param>
|
||||||
/// <returns>Markdown composition report section.</returns>
|
/// <returns>Markdown composition report section.</returns>
|
||||||
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.QueryCompositionReport(collection.QueryBreakdowns, Format);
|
||||||
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];
|
|
||||||
var paramSyntax = int.TryParse(paramName, out _)
|
|
||||||
? $"${paramName}"
|
|
||||||
: $":{paramName}";
|
|
||||||
sb.AppendLine($"- `{paramSyntax}` = `{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>
|
/// <summary>
|
||||||
/// Generates a batch execution flow diagram for PostgreSQL.
|
/// Generates a batch execution flow diagram for PostgreSQL.
|
||||||
@@ -306,76 +77,31 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="includeTransaction">Whether to show transaction wrapping.</param>
|
/// <param name="includeTransaction">Whether to show transaction wrapping.</param>
|
||||||
/// <returns>Mermaid diagram markdown.</returns>
|
/// <returns>Mermaid diagram markdown.</returns>
|
||||||
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
|
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
|
||||||
{
|
=> CollectionReportWriter.BatchFlowDiagram(
|
||||||
var sb = new StringBuilder();
|
collection.QueryBreakdowns.Count,
|
||||||
sb.AppendLine("```mermaid");
|
includeTransaction ? "BEGIN" : null,
|
||||||
sb.AppendLine("flowchart TD");
|
includeTransaction ? "COMMIT" : null);
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
int nodeId = 0;
|
/// <summary>
|
||||||
|
/// Maps the collection's parameter usage report into the writer's dialect-agnostic rows.
|
||||||
// Handle empty collection
|
/// </summary>
|
||||||
if (collection.QueryBreakdowns.Count == 0)
|
private static List<ParameterUsageRow> MapParameters(QueryBreakdownCollection collection)
|
||||||
{
|
=> collection.GetParameterUsageReport()
|
||||||
if (includeTransaction)
|
.Select(p => new ParameterUsageRow
|
||||||
{
|
{
|
||||||
sb.AppendLine($" Start([Batch Start]) --> node0[\"BEGIN\"]");
|
ParameterName = p.ParameterName,
|
||||||
sb.AppendLine($" node0 --> node1[\"COMMIT\"]");
|
IsUsedInAllQueries = p.IsUsedInAllQueries,
|
||||||
sb.AppendLine($" node1 --> End([Batch Complete])");
|
UsedInQueryCount = p.UsedInQueryCount,
|
||||||
}
|
TotalQueries = p.TotalQueries,
|
||||||
else
|
Value = p.Value
|
||||||
{
|
})
|
||||||
sb.AppendLine($" Start([Batch Start]) --> End([Batch Complete])");
|
.ToList();
|
||||||
}
|
|
||||||
sb.AppendLine("```");
|
|
||||||
sb.AppendLine();
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start node
|
/// <summary>
|
||||||
if (includeTransaction)
|
/// Formats a parameter using PostgreSQL syntax: <c>$n</c> for positional, <c>:name</c> for named.
|
||||||
{
|
/// </summary>
|
||||||
sb.AppendLine($" node{nodeId}[\"BEGIN\"]");
|
private static string FormatParameter(string name)
|
||||||
sb.AppendLine($" Start([Batch Start]) --> node{nodeId}");
|
=> int.TryParse(name, out _) ? $"${name}" : $":{name}";
|
||||||
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\"]");
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the parameter type name from a parameter value using PostgreSQL types.
|
/// Gets the parameter type name from a parameter value using PostgreSQL types.
|
||||||
@@ -397,36 +123,4 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
_ => "UNKNOWN"
|
_ => "UNKNOWN"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Strata.SqlTools.Breakdowns.Snowflake;
|
using Strata.SqlTools.Breakdowns.Snowflake;
|
||||||
|
using Strata.SqlTools.Markdown.Common;
|
||||||
|
|
||||||
namespace Strata.SqlTools.Markdown.Snowflake;
|
namespace Strata.SqlTools.Markdown.Snowflake;
|
||||||
|
|
||||||
@@ -10,6 +11,16 @@ namespace Strata.SqlTools.Markdown.Snowflake;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class QueryBreakdownCollectionGenerator
|
public static class QueryBreakdownCollectionGenerator
|
||||||
{
|
{
|
||||||
|
private static readonly MarkdownDialectFormat Format = new()
|
||||||
|
{
|
||||||
|
ParameterTableLabel = name => $":{name} / @{name}",
|
||||||
|
ParameterNodeLabel = name => $":{name}",
|
||||||
|
ParameterTypeName = GetParameterType,
|
||||||
|
ParameterNodeFill = "#e0f2f1",
|
||||||
|
QueryNodeFill = "#f1f8e9",
|
||||||
|
IncludeHavingRow = false
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a comprehensive collection report in Markdown format with Snowflake-specific information.
|
/// Generates a comprehensive collection report in Markdown format with Snowflake-specific information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -17,32 +28,13 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="title">Optional title for the report.</param>
|
/// <param name="title">Optional title for the report.</param>
|
||||||
/// <returns>A string containing the Markdown documentation.</returns>
|
/// <returns>A string containing the Markdown documentation.</returns>
|
||||||
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
||||||
{
|
=> CollectionReportWriter.CollectionReport(title, new[]
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(title))
|
|
||||||
{
|
{
|
||||||
sb.AppendLine($"# {title}");
|
GenerateCollectionSummary(collection),
|
||||||
sb.AppendLine();
|
GenerateSnowflakeFeaturesAnalysis(collection),
|
||||||
}
|
GenerateParameterAnalysis(collection),
|
||||||
|
GenerateQueryCompositionReport(collection)
|
||||||
// Collection Summary
|
});
|
||||||
sb.Append(GenerateCollectionSummary(collection));
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
// Snowflake Features Analysis
|
|
||||||
sb.Append(GenerateSnowflakeFeaturesAnalysis(collection));
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
// Parameter Analysis
|
|
||||||
sb.Append(GenerateParameterAnalysis(collection));
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
// Query Composition Report
|
|
||||||
sb.Append(GenerateQueryCompositionReport(collection));
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a summary section for the collection.
|
/// Generates a summary section for the collection.
|
||||||
@@ -50,20 +42,11 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
||||||
/// <returns>Markdown summary section.</returns>
|
/// <returns>Markdown summary section.</returns>
|
||||||
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.CollectionSummary(
|
||||||
var sb = new StringBuilder();
|
collection.QueryBreakdowns.Count,
|
||||||
sb.AppendLine("## Collection Summary");
|
collection.GetAllUniqueParameters().Count(),
|
||||||
sb.AppendLine();
|
collection.GetTotalSelectedColumns(),
|
||||||
|
collection.GetUniqueTableReferences().Count());
|
||||||
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>
|
/// <summary>
|
||||||
/// Generates a Snowflake-specific features analysis section.
|
/// Generates a Snowflake-specific features analysis section.
|
||||||
@@ -93,41 +76,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
||||||
/// <returns>Markdown parameter analysis section.</returns>
|
/// <returns>Markdown parameter analysis section.</returns>
|
||||||
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.ParameterAnalysis(MapParameters(collection), collection.QueryBreakdowns, Format);
|
||||||
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";
|
|
||||||
// Snowflake supports both : and @ syntax for parameters
|
|
||||||
sb.AppendLine($"| :{param.ParameterName} / @{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>
|
/// <summary>
|
||||||
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
||||||
@@ -135,84 +84,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
||||||
/// <returns>Mermaid diagram markdown.</returns>
|
/// <returns>Mermaid diagram markdown.</returns>
|
||||||
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.ParameterDependencyDiagram(collection.QueryBreakdowns, Format);
|
||||||
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:#e0f2f1");
|
|
||||||
}
|
|
||||||
|
|
||||||
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:#f1f8e9");
|
|
||||||
|
|
||||||
// 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>
|
/// <summary>
|
||||||
/// Generates a detailed query composition report with Snowflake-specific information.
|
/// Generates a detailed query composition report with Snowflake-specific information.
|
||||||
@@ -221,101 +93,41 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <returns>Markdown composition report section.</returns>
|
/// <returns>Markdown composition report section.</returns>
|
||||||
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.AppendLine("## Query Composition Report");
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
var summaries = collection.GetQuerySummaries().ToList();
|
|
||||||
var stageQueries = collection.WhereUseStageReference().ToList();
|
var stageQueries = collection.WhereUseStageReference().ToList();
|
||||||
var semiStructured = collection.WhereUseSemiStructuredData().ToList();
|
var semiStructured = collection.WhereUseSemiStructuredData().ToList();
|
||||||
|
|
||||||
for (int i = 0; i < summaries.Count; i++)
|
return CollectionReportWriter.QueryCompositionReport(
|
||||||
{
|
collection.QueryBreakdowns,
|
||||||
var summary = summaries[i];
|
Format,
|
||||||
var query = collection.QueryBreakdowns[i];
|
(sb, query) => AppendSnowflakeFeatures(sb, query, stageQueries, semiStructured));
|
||||||
|
|
||||||
AppendQueryCompositionTable(sb, i, summary);
|
|
||||||
AppendQueryParameters(sb, query);
|
|
||||||
AppendQueryCteSections(sb, summary, query);
|
|
||||||
AppendSnowflakeFeatures(sb, query, stageQueries, semiStructured);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends the query composition table for a single query.
|
/// Generates a batch execution flow diagram for Snowflake.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void AppendQueryCompositionTable(StringBuilder sb, int queryIndex, SnowflakeQueryAnalysis summary)
|
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
||||||
{
|
/// <param name="includeSessionSetup">Whether to show session setup statements.</param>
|
||||||
sb.AppendLine($"### Query #{queryIndex}");
|
/// <returns>Mermaid diagram markdown.</returns>
|
||||||
sb.AppendLine();
|
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeSessionSetup = false)
|
||||||
sb.AppendLine("| Aspect | Present |");
|
=> CollectionReportWriter.BatchFlowDiagram(
|
||||||
sb.AppendLine("|--------|---------|");
|
collection.QueryBreakdowns.Count,
|
||||||
sb.AppendLine($"| SELECT Clause | {FormatClausePresence(summary.HasSelectClause)} |");
|
includeSessionSetup ? "Session Setup" : null,
|
||||||
sb.AppendLine($"| FROM Clause | {FormatClausePresence(summary.HasFromClause)} |");
|
closeLabel: null);
|
||||||
sb.AppendLine($"| WHERE Clause | {FormatClausePresence(summary.HasWhereClause)} |");
|
|
||||||
sb.AppendLine($"| GROUP BY Clause | {FormatClausePresence(summary.HasGroupByClause)} |");
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Appends parameter information for a query.
|
/// Maps the collection's parameter usage report into the writer's dialect-agnostic rows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void AppendQueryParameters(StringBuilder sb, QueryBreakdown query)
|
private static List<ParameterUsageRow> MapParameters(QueryBreakdownCollection collection)
|
||||||
{
|
=> collection.GetParameterUsageReport()
|
||||||
// Collect all unique parameters from both ParameterList and Parameters dictionary
|
.Select(p => new ParameterUsageRow
|
||||||
var allParams = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
{
|
||||||
|
ParameterName = p.ParameterName,
|
||||||
// Add from ParameterList (parsed parameters)
|
IsUsedInAllQueries = p.IsUsedInAllQueries,
|
||||||
foreach (var param in query.ParameterList)
|
UsedInQueryCount = p.UsedInQueryCount,
|
||||||
{
|
TotalQueries = p.TotalQueries,
|
||||||
allParams[param.Name] = param.Value;
|
Value = p.Value
|
||||||
}
|
})
|
||||||
|
.ToList();
|
||||||
// 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, SnowflakeQueryAnalysis 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>
|
/// <summary>
|
||||||
/// Appends Snowflake-specific feature information for a query.
|
/// Appends Snowflake-specific feature information for a query.
|
||||||
@@ -336,80 +148,13 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Formats clause presence as Yes/No with checkmark/cross.
|
/// Formats feature presence as Yes/No with checkmark/cross.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string FormatClausePresence(bool isPresent)
|
private static string FormatFeaturePresence(bool isPresent)
|
||||||
=> isPresent ? "✓ Yes" : "✗ No";
|
=> isPresent ? "✓ Yes" : "✗ No";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a batch execution flow diagram for Snowflake.
|
/// Gets the parameter type name from a parameter value using Snowflake types.
|
||||||
/// </summary>
|
|
||||||
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
|
||||||
/// <param name="includeSessionSetup">Whether to show session setup statements.</param>
|
|
||||||
/// <returns>Mermaid diagram markdown.</returns>
|
|
||||||
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeSessionSetup = false)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.AppendLine("```mermaid");
|
|
||||||
sb.AppendLine("flowchart TD");
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
// Handle empty collection
|
|
||||||
if (collection.QueryBreakdowns.Count == 0)
|
|
||||||
{
|
|
||||||
if (includeSessionSetup)
|
|
||||||
{
|
|
||||||
sb.AppendLine($" Start([Batch Start]) --> node0[\"Session Setup\"]");
|
|
||||||
sb.AppendLine($" node0 --> End([Batch Complete])");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.AppendLine($" Start([Batch Start]) --> End([Batch Complete])");
|
|
||||||
}
|
|
||||||
sb.AppendLine("```");
|
|
||||||
sb.AppendLine();
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
int nodeId = 0;
|
|
||||||
|
|
||||||
// Start node
|
|
||||||
if (includeSessionSetup)
|
|
||||||
{
|
|
||||||
sb.AppendLine($" node{nodeId}[\"Session Setup\"]");
|
|
||||||
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
|
|
||||||
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>
|
/// </summary>
|
||||||
private static string GetParameterType(object? value)
|
private static string GetParameterType(object? value)
|
||||||
{
|
{
|
||||||
@@ -424,36 +169,4 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
_ => "VARIANT"
|
_ => "VARIANT"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Escapes special Markdown characters.
|
|
||||||
/// </summary>
|
|
||||||
private static string EscapeMarkdown(string text)
|
|
||||||
{
|
|
||||||
return text
|
|
||||||
.Replace("\\", "\\\\")
|
|
||||||
.Replace("|", "\\|")
|
|
||||||
.Replace("\n", "\\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Formats feature presence as Yes/No with checkmark/cross.
|
|
||||||
/// </summary>
|
|
||||||
private static string FormatFeaturePresence(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";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Text;
|
|
||||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||||
|
using Strata.SqlTools.Markdown.Common;
|
||||||
|
|
||||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||||
|
|
||||||
@@ -9,6 +9,16 @@ namespace Strata.SqlTools.Markdown.SqlServer;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class QueryBreakdownCollectionGenerator
|
public static class QueryBreakdownCollectionGenerator
|
||||||
{
|
{
|
||||||
|
private static readonly MarkdownDialectFormat Format = new()
|
||||||
|
{
|
||||||
|
ParameterTableLabel = name => $"@{name}",
|
||||||
|
ParameterNodeLabel = name => $"@{name}",
|
||||||
|
ParameterTypeName = GetParameterType,
|
||||||
|
ParameterNodeFill = "#e1f5ff",
|
||||||
|
QueryNodeFill = "#f3e5f5",
|
||||||
|
IncludeHavingRow = true
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a comprehensive collection report in Markdown format.
|
/// Generates a comprehensive collection report in Markdown format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -16,28 +26,12 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="title">Optional title for the report.</param>
|
/// <param name="title">Optional title for the report.</param>
|
||||||
/// <returns>A string containing the Markdown documentation.</returns>
|
/// <returns>A string containing the Markdown documentation.</returns>
|
||||||
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
|
||||||
{
|
=> CollectionReportWriter.CollectionReport(title, new[]
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(title))
|
|
||||||
{
|
{
|
||||||
sb.AppendLine($"# {title}");
|
GenerateCollectionSummary(collection),
|
||||||
sb.AppendLine();
|
GenerateParameterAnalysis(collection),
|
||||||
}
|
GenerateQueryCompositionReport(collection)
|
||||||
|
});
|
||||||
// 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>
|
/// <summary>
|
||||||
/// Generates a summary section for the collection.
|
/// Generates a summary section for the collection.
|
||||||
@@ -45,20 +39,11 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
|
||||||
/// <returns>Markdown summary section.</returns>
|
/// <returns>Markdown summary section.</returns>
|
||||||
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.CollectionSummary(
|
||||||
var sb = new StringBuilder();
|
collection.QueryBreakdowns.Count,
|
||||||
sb.AppendLine("## Collection Summary");
|
collection.GetAllUniqueParameters().Count(),
|
||||||
sb.AppendLine();
|
collection.GetTotalSelectedColumns(),
|
||||||
|
collection.GetUniqueTableReferences().Count());
|
||||||
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>
|
/// <summary>
|
||||||
/// Generates a parameter analysis report.
|
/// Generates a parameter analysis report.
|
||||||
@@ -66,41 +51,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
|
||||||
/// <returns>Markdown parameter analysis section.</returns>
|
/// <returns>Markdown parameter analysis section.</returns>
|
||||||
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.ParameterAnalysis(MapParameters(collection), collection.QueryBreakdowns, Format);
|
||||||
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>
|
/// <summary>
|
||||||
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
/// Generates a Mermaid diagram showing parameter dependencies across queries.
|
||||||
@@ -108,84 +59,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
|
||||||
/// <returns>Mermaid diagram markdown.</returns>
|
/// <returns>Mermaid diagram markdown.</returns>
|
||||||
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.ParameterDependencyDiagram(collection.QueryBreakdowns, Format);
|
||||||
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>
|
/// <summary>
|
||||||
/// Generates a detailed query composition report.
|
/// Generates a detailed query composition report.
|
||||||
@@ -193,100 +67,7 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="collection">The QueryBreakdownCollection to report on.</param>
|
/// <param name="collection">The QueryBreakdownCollection to report on.</param>
|
||||||
/// <returns>Markdown composition report section.</returns>
|
/// <returns>Markdown composition report section.</returns>
|
||||||
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
|
||||||
{
|
=> CollectionReportWriter.QueryCompositionReport(collection.QueryBreakdowns, Format);
|
||||||
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>
|
/// <summary>
|
||||||
/// Generates a batch execution flow diagram.
|
/// Generates a batch execution flow diagram.
|
||||||
@@ -295,76 +76,25 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
/// <param name="includeTransaction">Whether to show transaction wrapping.</param>
|
/// <param name="includeTransaction">Whether to show transaction wrapping.</param>
|
||||||
/// <returns>Mermaid diagram markdown.</returns>
|
/// <returns>Mermaid diagram markdown.</returns>
|
||||||
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
|
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
|
||||||
{
|
=> CollectionReportWriter.BatchFlowDiagram(
|
||||||
var sb = new StringBuilder();
|
collection.QueryBreakdowns.Count,
|
||||||
sb.AppendLine("```mermaid");
|
includeTransaction ? "BEGIN TRANSACTION" : null,
|
||||||
sb.AppendLine("flowchart TD");
|
includeTransaction ? "COMMIT TRANSACTION" : null);
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
int nodeId = 0;
|
/// <summary>
|
||||||
|
/// Maps the collection's parameter usage report into the writer's dialect-agnostic rows.
|
||||||
// Handle empty collection
|
/// </summary>
|
||||||
if (collection.QueryBreakdowns.Count == 0)
|
private static List<ParameterUsageRow> MapParameters(QueryBreakdownCollection collection)
|
||||||
{
|
=> collection.GetParameterUsageReport()
|
||||||
if (includeTransaction)
|
.Select(p => new ParameterUsageRow
|
||||||
{
|
{
|
||||||
sb.AppendLine($" Start([Batch Start]) --> node0[\"BEGIN TRANSACTION\"]");
|
ParameterName = p.ParameterName,
|
||||||
sb.AppendLine($" node0 --> node1[\"COMMIT TRANSACTION\"]");
|
IsUsedInAllQueries = p.IsUsedInAllQueries,
|
||||||
sb.AppendLine($" node1 --> End([Batch Complete])");
|
UsedInQueryCount = p.UsedInQueryCount,
|
||||||
}
|
TotalQueries = p.TotalQueries,
|
||||||
else
|
Value = p.Value
|
||||||
{
|
})
|
||||||
sb.AppendLine($" Start([Batch Start]) --> End([Batch Complete])");
|
.ToList();
|
||||||
}
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the parameter type name from a parameter value.
|
/// Gets the parameter type name from a parameter value.
|
||||||
@@ -387,36 +117,4 @@ public static class QueryBreakdownCollectionGenerator
|
|||||||
_ => "VARIANT"
|
_ => "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";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public QueryBreakdownCollection() : base()
|
public QueryBreakdownCollection() : base()
|
||||||
{
|
{
|
||||||
_queryBreakdowns = new List<QueryBreakdown>();
|
_queryBreakdowns = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,7 +31,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
|||||||
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns)
|
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||||
: base(queryBreakdowns?.Cast<ISqlBreakdown>() ?? Enumerable.Empty<ISqlBreakdown>())
|
: base(queryBreakdowns?.Cast<ISqlBreakdown>() ?? Enumerable.Empty<ISqlBreakdown>())
|
||||||
{
|
{
|
||||||
_queryBreakdowns = queryBreakdowns?.ToList() ?? new List<QueryBreakdown>();
|
_queryBreakdowns = queryBreakdowns?.ToList() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,7 +58,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
|||||||
/// <param name="queryBreakdowns">The QueryBreakdowns to add.</param>
|
/// <param name="queryBreakdowns">The QueryBreakdowns to add.</param>
|
||||||
public void AddRange(IEnumerable<QueryBreakdown> queryBreakdowns)
|
public void AddRange(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||||
{
|
{
|
||||||
foreach (var qb in queryBreakdowns ?? new List<QueryBreakdown>())
|
foreach (var qb in queryBreakdowns ?? [])
|
||||||
{
|
{
|
||||||
Add(qb);
|
Add(qb);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user