Добавлены mermaid диаграммы
This commit is contained in:
402
SQLLinter/Infrastructure/Diagram/BpmnBuilder.cs
Normal file
402
SQLLinter/Infrastructure/Diagram/BpmnBuilder.cs
Normal file
@@ -0,0 +1,402 @@
|
||||
using Microsoft.SqlServer.TransactSql.ScriptDom;
|
||||
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
public enum BpmnNodeType
|
||||
{
|
||||
Start,
|
||||
End,
|
||||
Task,
|
||||
Gateway,
|
||||
Subprocess,
|
||||
}
|
||||
|
||||
public class BpmnNode
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Label { get; set; } = string.Empty;
|
||||
public BpmnNodeType Type { get; set; }
|
||||
}
|
||||
|
||||
public class BpmnEdge
|
||||
{
|
||||
public string From { get; set; } = string.Empty;
|
||||
public string To { get; set; } = string.Empty;
|
||||
public string Label { get; set; } = string.Empty;
|
||||
public bool Dashed { get; set; }
|
||||
}
|
||||
|
||||
public class BpmnProcess
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<BpmnNode> Nodes { get; set; } = new();
|
||||
public List<BpmnEdge> Edges { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BpmnDiagram
|
||||
{
|
||||
public BpmnProcess Main { get; set; } = new BpmnProcess { Id = "Main", Name = "Main" };
|
||||
public List<BpmnProcess> Subprocesses { get; set; } = new();
|
||||
}
|
||||
|
||||
public static class BpmnBuilder
|
||||
{
|
||||
public static BpmnDiagram Build(TSqlFragment fragment)
|
||||
{
|
||||
var visitor = new BpmnVisitor();
|
||||
fragment.Accept(visitor);
|
||||
return visitor.Diagram;
|
||||
}
|
||||
|
||||
private class BpmnVisitor : TSqlFragmentVisitor
|
||||
{
|
||||
public BpmnDiagram Diagram { get; } = new BpmnDiagram();
|
||||
|
||||
private Dictionary<string, string> _lastNodeByProcess = new();
|
||||
private BpmnProcess _currentProcess;
|
||||
private int _nodeCounter = 0;
|
||||
|
||||
public BpmnVisitor()
|
||||
{
|
||||
_currentProcess = Diagram.Main;
|
||||
// create a Start node for main
|
||||
var start = new BpmnNode { Id = NewId(), Label = "Start", Type = BpmnNodeType.Start };
|
||||
Diagram.Main.Nodes.Add(start);
|
||||
_lastNodeByProcess[Diagram.Main.Id] = start.Id;
|
||||
}
|
||||
|
||||
private string NewId() => "n" + (_nodeCounter++);
|
||||
|
||||
private void AddNode(BpmnProcess proc, BpmnNode node)
|
||||
{
|
||||
if (string.IsNullOrEmpty(node.Id)) node.Id = NewId();
|
||||
proc.Nodes.Add(node);
|
||||
|
||||
if (_lastNodeByProcess.TryGetValue(proc.Id, out var last) && !string.IsNullOrEmpty(last))
|
||||
{
|
||||
proc.Edges.Add(new BpmnEdge { From = last, To = node.Id });
|
||||
}
|
||||
_lastNodeByProcess[proc.Id] = node.Id;
|
||||
}
|
||||
|
||||
private void AddNodeToCurrent(string label, BpmnNodeType type)
|
||||
{
|
||||
var node = new BpmnNode { Id = NewId(), Label = label, Type = type };
|
||||
AddNode(_currentProcess, node);
|
||||
}
|
||||
|
||||
public override void Visit(TSqlScript node)
|
||||
{
|
||||
_currentProcess = Diagram.Main;
|
||||
if (!_lastNodeByProcess.ContainsKey(_currentProcess.Id)) _lastNodeByProcess[_currentProcess.Id] = null;
|
||||
base.Visit(node);
|
||||
|
||||
// add End node for main process
|
||||
var end = new BpmnNode { Id = NewId(), Label = "End", Type = BpmnNodeType.End };
|
||||
AddNode(Diagram.Main, end);
|
||||
}
|
||||
|
||||
public override void Visit(CreateProcedureStatement node)
|
||||
{
|
||||
// AST-based extraction of procedure name
|
||||
var name = FindSchemaObjectName(node) ?? ("proc" + Diagram.Subprocesses.Count);
|
||||
var pid = SanitizeId(name);
|
||||
var proc = new BpmnProcess { Id = pid, Name = name };
|
||||
|
||||
// synthetic pool/label node
|
||||
var poolNode = new BpmnNode { Id = pid + "_pool", Label = name, Type = BpmnNodeType.Subprocess };
|
||||
proc.Nodes.Add(poolNode);
|
||||
// create start node for subprocess
|
||||
var start = new BpmnNode { Id = pid + "_start", Label = "Start", Type = BpmnNodeType.Start };
|
||||
proc.Nodes.Add(start);
|
||||
// set last node pointer to start
|
||||
_lastNodeByProcess[proc.Id] = start.Id;
|
||||
|
||||
Diagram.Subprocesses.Add(proc);
|
||||
|
||||
var old = _currentProcess;
|
||||
_currentProcess = proc;
|
||||
if (!_lastNodeByProcess.ContainsKey(proc.Id)) _lastNodeByProcess[proc.Id] = start.Id;
|
||||
|
||||
base.Visit(node);
|
||||
|
||||
// after visiting procedure body add End node
|
||||
var end = new BpmnNode { Id = pid + "_end", Label = "End", Type = BpmnNodeType.End };
|
||||
AddNode(proc, end);
|
||||
|
||||
_currentProcess = old;
|
||||
}
|
||||
|
||||
public override void Visit(ExecuteStatement node)
|
||||
{
|
||||
// AST-based extraction for called proc
|
||||
var called = FindProcedureNameFromExecute(node);
|
||||
var label = called != null ? $"EXEC {called}" : node.ToString();
|
||||
|
||||
// task in current process
|
||||
var task = new BpmnNode { Id = NewId(), Label = label, Type = BpmnNodeType.Task };
|
||||
AddNode(_currentProcess, task);
|
||||
|
||||
// if calling known subprocess, add dashed edge from this node to subprocess start
|
||||
if (called != null)
|
||||
{
|
||||
var proc = Diagram.Subprocesses.FirstOrDefault(p => string.Equals(p.Name, called, StringComparison.OrdinalIgnoreCase) || string.Equals(p.Id, SanitizeId(called), StringComparison.OrdinalIgnoreCase));
|
||||
if (proc != null)
|
||||
{
|
||||
// add dashed edge in main process (from this task to subprocess pool node)
|
||||
Diagram.Main.Edges.Add(new BpmnEdge { From = task.Id, To = proc.Nodes.First().Id, Dashed = true });
|
||||
}
|
||||
}
|
||||
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(IfStatement node)
|
||||
{
|
||||
var gid = new BpmnNode { Id = NewId(), Label = "IF", Type = BpmnNodeType.Gateway };
|
||||
AddNode(_currentProcess, gid);
|
||||
|
||||
// then / else placeholders
|
||||
if (node.ThenStatement != null)
|
||||
{
|
||||
var thenNode = new BpmnNode { Id = NewId(), Label = "THEN", Type = BpmnNodeType.Task };
|
||||
AddNode(_currentProcess, thenNode);
|
||||
_currentProcess.Edges.Add(new BpmnEdge { From = gid.Id, To = thenNode.Id });
|
||||
}
|
||||
|
||||
if (node.ElseStatement != null)
|
||||
{
|
||||
var elseNode = new BpmnNode { Id = NewId(), Label = "ELSE", Type = BpmnNodeType.Task };
|
||||
AddNode(_currentProcess, elseNode);
|
||||
_currentProcess.Edges.Add(new BpmnEdge { From = gid.Id, To = elseNode.Id });
|
||||
}
|
||||
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(SelectStatement node)
|
||||
{
|
||||
var label = "SELECT";
|
||||
if (node.QueryExpression is QuerySpecification qs && qs.SelectElements != null)
|
||||
{
|
||||
label = $"SELECT ({qs.SelectElements.Count})";
|
||||
}
|
||||
AddNodeToCurrent(label, BpmnNodeType.Task);
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(InsertStatement node)
|
||||
{
|
||||
var target = FindTargetNameForDml(node);
|
||||
var label = "INSERT" + (string.IsNullOrEmpty(target) ? string.Empty : " -> " + target);
|
||||
AddNodeToCurrent(label, BpmnNodeType.Task);
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(UpdateStatement node)
|
||||
{
|
||||
var target = FindTargetNameForDml(node);
|
||||
var label = "UPDATE" + (string.IsNullOrEmpty(target) ? string.Empty : " -> " + target);
|
||||
AddNodeToCurrent(label, BpmnNodeType.Task);
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(DeleteStatement node)
|
||||
{
|
||||
var target = FindTargetNameForDml(node);
|
||||
var label = "DELETE" + (string.IsNullOrEmpty(target) ? string.Empty : " -> " + target);
|
||||
AddNodeToCurrent(label, BpmnNodeType.Task);
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
// AST helpers
|
||||
private static string? FindSchemaObjectName(TSqlFragment fragment)
|
||||
{
|
||||
var finder = new SchemaNameFinder();
|
||||
fragment.Accept(finder);
|
||||
return finder.FoundName;
|
||||
}
|
||||
|
||||
private static string? FindProcedureNameFromExecute(ExecuteStatement node)
|
||||
{
|
||||
// Prefer ProcedureReference inside the statement
|
||||
var finder = new ProcedureReferenceFinder();
|
||||
node.Accept(finder);
|
||||
return finder.FoundName;
|
||||
}
|
||||
|
||||
private static string? FindTargetNameForDml(TSqlFragment fragment)
|
||||
{
|
||||
var finder = new TargetTableFinder();
|
||||
fragment.Accept(finder);
|
||||
return finder.FoundName;
|
||||
}
|
||||
|
||||
private class SchemaNameFinder : TSqlFragmentVisitor
|
||||
{
|
||||
public string? FoundName { get; private set; }
|
||||
public override void Visit(SchemaObjectName node)
|
||||
{
|
||||
if (FoundName == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node.Identifiers != null && node.Identifiers.Count > 0)
|
||||
{
|
||||
FoundName = string.Join('.', node.Identifiers.Select(id => id.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
FoundName = node.ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
FoundName = node.ToString();
|
||||
}
|
||||
}
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(ProcedureReferenceName node)
|
||||
{
|
||||
if (FoundName == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node.ProcedureReference != null && node.ProcedureReference.Name != null)
|
||||
{
|
||||
// procedure reference name may contain identifiers
|
||||
FoundName = node.ProcedureReference.Name.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
FoundName = node.ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
FoundName = node.ToString();
|
||||
}
|
||||
}
|
||||
base.Visit(node);
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcedureReferenceFinder : TSqlFragmentVisitor
|
||||
{
|
||||
public string? FoundName { get; private set; }
|
||||
public override void Visit(ProcedureReference node)
|
||||
{
|
||||
if (FoundName == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node.Name != null)
|
||||
{
|
||||
if (node.Name.Identifiers != null && node.Name.Identifiers.Count > 0)
|
||||
FoundName = string.Join('.', node.Name.Identifiers.Select(id => id.Value));
|
||||
else
|
||||
FoundName = node.Name.ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
FoundName = node.ToString();
|
||||
}
|
||||
}
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(ExecuteSpecification node)
|
||||
{
|
||||
base.Visit(node);
|
||||
}
|
||||
}
|
||||
|
||||
private class TargetTableFinder : TSqlFragmentVisitor
|
||||
{
|
||||
public string? FoundName { get; private set; }
|
||||
private int _bestDepth = int.MaxValue;
|
||||
private int _depth = 0;
|
||||
|
||||
public override void Visit(TSqlFragment node)
|
||||
{
|
||||
_depth++;
|
||||
base.Visit(node);
|
||||
_depth--;
|
||||
}
|
||||
|
||||
public override void Visit(NamedTableReference node)
|
||||
{
|
||||
if (node == null) return;
|
||||
int depth = _depth;
|
||||
if (depth < _bestDepth)
|
||||
{
|
||||
_bestDepth = depth;
|
||||
try
|
||||
{
|
||||
if (node.SchemaObject != null && node.SchemaObject.Identifiers != null && node.SchemaObject.Identifiers.Count > 0)
|
||||
FoundName = string.Join('.', node.SchemaObject.Identifiers.Select(id => id.Value));
|
||||
else
|
||||
FoundName = node.SchemaObject?.ToString() ?? node.ToString();
|
||||
|
||||
if (node.Alias != null && !string.IsNullOrWhiteSpace(node.Alias.Value))
|
||||
FoundName += " AS " + node.Alias.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
FoundName = node.SchemaObject?.ToString() ?? node.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(VariableTableReference node)
|
||||
{
|
||||
if (node == null) return;
|
||||
int depth = _depth;
|
||||
if (depth < _bestDepth)
|
||||
{
|
||||
_bestDepth = depth;
|
||||
FoundName = node.Variable?.ToString();
|
||||
}
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(QueryDerivedTable node)
|
||||
{
|
||||
if (node == null) return;
|
||||
int depth = _depth;
|
||||
if (depth < _bestDepth && node.Alias != null && !string.IsNullOrWhiteSpace(node.Alias.Value))
|
||||
{
|
||||
_bestDepth = depth;
|
||||
FoundName = node.Alias.Value + " (derived)";
|
||||
}
|
||||
|
||||
// still traverse inside
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public override void Visit(CommonTableExpression node)
|
||||
{
|
||||
// CTE definitions: prefer not to pick CTE as target unless no other table found
|
||||
base.Visit(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static string SanitizeId(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "id";
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var ch in s)
|
||||
{
|
||||
if (char.IsLetterOrDigit(ch)) sb.Append(ch);
|
||||
else sb.Append('_');
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
51
SQLLinter/Infrastructure/Diagram/FragmentDiagramBuilder.cs
Normal file
51
SQLLinter/Infrastructure/Diagram/FragmentDiagramBuilder.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Text;
|
||||
using Microsoft.SqlServer.TransactSql.ScriptDom;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
public static class FragmentDiagramBuilder
|
||||
{
|
||||
public static string RenderMermaid(TSqlFragment fragment)
|
||||
{
|
||||
if (fragment == null) return string.Empty;
|
||||
var diagram = BpmnBuilder.Build(fragment);
|
||||
return MermaidRenderer.RenderMarkdown(diagram);
|
||||
}
|
||||
|
||||
public static string RenderHtmlSvg(TSqlFragment fragment)
|
||||
{
|
||||
if (fragment == null) return string.Empty;
|
||||
var diagram = BpmnBuilder.Build(fragment);
|
||||
// Use mermaid HTML instead of SVG fallback
|
||||
return MermaidRenderer.RenderHtml(diagram);
|
||||
}
|
||||
|
||||
// keep helpers used by earlier code if needed
|
||||
private static string Escape(string s)
|
||||
{
|
||||
if (s == null) return string.Empty;
|
||||
return System.Net.WebUtility.HtmlEncode(s).Replace("\n", " ").Replace("\r", " ");
|
||||
}
|
||||
|
||||
private static string Truncate(string s, int len)
|
||||
{
|
||||
if (s == null) return string.Empty;
|
||||
if (s.Length <= len) return s;
|
||||
return s.Substring(0, len - 3) + "...";
|
||||
}
|
||||
|
||||
private static string SanitizeId(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "id";
|
||||
var sb = new StringBuilder();
|
||||
foreach (var ch in s)
|
||||
{
|
||||
if (char.IsLetterOrDigit(ch)) sb.Append(ch);
|
||||
else sb.Append('_');
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
147
SQLLinter/Infrastructure/Diagram/MermaidRenderer.cs
Normal file
147
SQLLinter/Infrastructure/Diagram/MermaidRenderer.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System.Text;
|
||||
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
public static class MermaidRenderer
|
||||
{
|
||||
public static string ToMermaidContent(BpmnDiagram diagram)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("flowchart TB");
|
||||
|
||||
// main: print Start, then tasks/gateways, then End
|
||||
var mainStart = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.Start).ToList();
|
||||
var mainTasks = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.Task || n.Type == BpmnNodeType.Gateway).ToList();
|
||||
var mainEnd = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.End).ToList();
|
||||
|
||||
foreach (var node in mainStart)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
foreach (var node in mainTasks)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
if (node.Type == BpmnNodeType.Gateway)
|
||||
sb.AppendLine($" {nid}{{\"{label}\"}}");
|
||||
else
|
||||
sb.AppendLine($" {nid}[\"{label}\"]");
|
||||
}
|
||||
foreach (var node in mainEnd)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
// subprocesses as subgraphs: pool label, start, tasks/gateways, end
|
||||
foreach (var proc in diagram.Subprocesses)
|
||||
{
|
||||
var procId = SanitizeId(proc.Id);
|
||||
sb.AppendLine($" subgraph {procId} [\"{Escape(proc.Name)}\"]");
|
||||
|
||||
var startNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Start).ToList();
|
||||
var taskNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Task || n.Type == BpmnNodeType.Gateway).ToList();
|
||||
var endNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.End).ToList();
|
||||
var poolNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Subprocess).ToList();
|
||||
|
||||
// pool label nodes (usually first)
|
||||
foreach (var node in poolNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}[\"{label}\"]");
|
||||
}
|
||||
|
||||
foreach (var node in startNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
foreach (var node in taskNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
if (node.Type == BpmnNodeType.Gateway)
|
||||
sb.AppendLine($" {nid}{{\"{label}\"}}");
|
||||
else
|
||||
sb.AppendLine($" {nid}[\"{label}\"]");
|
||||
}
|
||||
foreach (var node in endNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
|
||||
// edges inside subprocess
|
||||
foreach (var e in proc.Edges)
|
||||
{
|
||||
var from = SanitizeId(e.From);
|
||||
var to = SanitizeId(e.To);
|
||||
var arrow = e.Dashed ? "-.->" : "-->";
|
||||
var lbl = string.IsNullOrWhiteSpace(e.Label) ? string.Empty : $" |{Escape(e.Label)}|";
|
||||
sb.AppendLine($" {from} {arrow} {to}{lbl}");
|
||||
}
|
||||
|
||||
sb.AppendLine(" end");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// main edges
|
||||
foreach (var e in diagram.Main.Edges)
|
||||
{
|
||||
var from = SanitizeId(e.From);
|
||||
var to = SanitizeId(e.To);
|
||||
var arrow = e.Dashed ? "-.->" : "-->";
|
||||
var lbl = string.IsNullOrWhiteSpace(e.Label) ? string.Empty : $" |{Escape(e.Label)}|";
|
||||
sb.AppendLine($" {from} {arrow} {to}{lbl}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string RenderMarkdown(BpmnDiagram diagram)
|
||||
{
|
||||
var content = ToMermaidContent(diagram);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("```mermaid");
|
||||
sb.Append(content);
|
||||
sb.AppendLine("```");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string RenderHtml(BpmnDiagram diagram)
|
||||
{
|
||||
var content = ToMermaidContent(diagram);
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Put raw mermaid text inside .mermaid container so mermaid.js can render it
|
||||
sb.AppendLine("<div class=\"mermaid\">\n" + content + "\n</div>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string Escape(string s)
|
||||
{
|
||||
if (s == null) return string.Empty;
|
||||
return s.Replace("\"", "\\\"").Replace("\n", " ").Replace("\r", " ");
|
||||
}
|
||||
|
||||
private static string SanitizeId(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "id";
|
||||
var sb = new StringBuilder();
|
||||
foreach (var ch in s)
|
||||
{
|
||||
if (char.IsLetterOrDigit(ch)) sb.Append(ch);
|
||||
else sb.Append('_');
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
73
SQLLinter/Infrastructure/Diagram/SqlDiagramProcessor.cs
Normal file
73
SQLLinter/Infrastructure/Diagram/SqlDiagramProcessor.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using SQLLinter.Core.Interfaces;
|
||||
using SQLLinter.Infrastructure.Interfaces;
|
||||
using SQLLinter.Infrastructure.Parser;
|
||||
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
public class SqlDiagramProcessor : ISqlDiagramProcessor
|
||||
{
|
||||
private readonly BpmnDiagram _bpmnDiagram;
|
||||
private readonly IFragmentBuilder _fragmentBuilder;
|
||||
private readonly ISqlStreamReaderBuilder _sqlStreamReaderBuilder;
|
||||
|
||||
public SqlDiagramProcessor(IFragmentBuilder fragmentBuilder, BpmnDiagram bpmnDiagram)
|
||||
: this(fragmentBuilder, bpmnDiagram, new SqlStreamReaderBuilder()) { }
|
||||
|
||||
public SqlDiagramProcessor(IFragmentBuilder fragmentBuilder, BpmnDiagram bpmnDiagram, ISqlStreamReaderBuilder sqlStreamReaderBuilder)
|
||||
{
|
||||
_fragmentBuilder = fragmentBuilder;
|
||||
_bpmnDiagram = bpmnDiagram;
|
||||
_sqlStreamReaderBuilder = sqlStreamReaderBuilder;
|
||||
|
||||
}
|
||||
|
||||
public void ProcessList(List<string> filePaths)
|
||||
{
|
||||
foreach (var path in filePaths)
|
||||
{
|
||||
ProcessFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessPath(string path)
|
||||
{
|
||||
ProcessFile(path);
|
||||
}
|
||||
|
||||
private void ProcessFile(string filePath)
|
||||
{
|
||||
var fileStream = GetFileContents(filePath);
|
||||
HandleProcessing(filePath, fileStream);
|
||||
}
|
||||
|
||||
public void ProcessList(Dictionary<string, Stream> files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
HandleProcessing(file.Key, file.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleProcessing(string filePath, Stream fileStream)
|
||||
{
|
||||
var fragment = _fragmentBuilder.GetFragment(filePath, GetSqlTextReader(fileStream), out var errors);
|
||||
|
||||
if (fragment == null || errors.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var diagramm = BpmnBuilder.Build(fragment);
|
||||
_bpmnDiagram.Subprocesses.Add(diagramm.Main);
|
||||
}
|
||||
|
||||
private Stream GetFileContents(string filePath)
|
||||
{
|
||||
return File.OpenRead(filePath);
|
||||
}
|
||||
|
||||
private StreamReader GetSqlTextReader(Stream sqlFileStream)
|
||||
{
|
||||
return _sqlStreamReaderBuilder.CreateReader(sqlFileStream);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user