Добавьте файлы проекта.

This commit is contained in:
FrigaT
2026-01-05 00:29:19 +03:00
committed by FrigaT
parent 76a09d80d4
commit d0653c2098
105 changed files with 6729 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
using Microsoft.Extensions.Logging;
using SQLVision.Core.Enums;
using SQLVision.Core.Interfaces;
using SQLVision.Core.Models;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace SQLVision.Services.Parsers;
public class SqlScriptParser : ISqlScriptParser
{
private readonly ILogger<SqlScriptParser> _logger;
private readonly JsonSerializerOptions _jsonOptions;
public SqlScriptParser(ILogger<SqlScriptParser> logger)
{
_logger = logger;
_jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
}
public ScriptMetadata Parse(string filePath, string sqlContent)
{
var metadata = new ScriptMetadata
{
FileName = Path.GetFileName(filePath),
FullPath = filePath,
RawSql = sqlContent,
LastModified = File.GetLastWriteTimeUtc(filePath)
};
var lines = sqlContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
var sqlBuilder = new StringBuilder();
var inMultilineComment = false;
foreach (var line in lines)
{
var trimmedLine = line.Trim();
// Обработка многострочных комментариев
if (trimmedLine.StartsWith("/*"))
{
inMultilineComment = true;
if (trimmedLine.Contains("*/"))
{
inMultilineComment = false;
ProcessInlineMultilineComment(trimmedLine, metadata);
}
else
{
ProcessMultilineCommentStart(trimmedLine, metadata);
}
continue;
}
if (inMultilineComment)
{
if (trimmedLine.Contains("*/"))
{
inMultilineComment = false;
ProcessMultilineCommentEnd(trimmedLine, metadata);
}
else
{
ProcessMultilineCommentContent(trimmedLine, metadata);
}
continue;
}
// Обработка однострочных комментариев
if (trimmedLine.StartsWith("--"))
{
ProcessSingleLineComment(trimmedLine, metadata);
}
else
{
sqlBuilder.AppendLine(line);
}
}
metadata.ProcessedSql = sqlBuilder.ToString();
ExtractCategoryAndTags(metadata);
return metadata;
}
private void ProcessSingleLineComment(string line, ScriptMetadata metadata)
{
// Удаляем "--" и триммируем
var content = line.Substring(2).Trim();
// Проверяем на директивы
if (content.StartsWith("@"))
{
ProcessDirective(content, metadata);
}
else if (string.IsNullOrEmpty(metadata.Description))
{
// Первый комментарий без директивы - это описание
metadata.Description = content;
}
}
private void ProcessDirective(string content, ScriptMetadata metadata) // Убрали ref
{
// Убираем "@"
content = content.Substring(1).Trim();
var spaceIndex = content.IndexOf(' ');
if (spaceIndex <= 0) return;
var directive = content.Substring(0, spaceIndex).ToLower();
var value = content.Substring(spaceIndex + 1).Trim();
try
{
switch (directive)
{
case "description":
metadata.Description = value.Trim('"');
break;
case "param":
var param = ParseParameter(value);
if (param != null)
metadata.Parameters.Add(param);
break;
case "output":
var output = ParseOutput(value);
if (output != null)
metadata.Outputs.Add(output);
break;
case "connection":
metadata.ConnectionString = value.Trim('"');
break;
case "database":
if (Enum.TryParse<DatabaseProvider>(value, true, out var provider))
metadata.DatabaseProvider = provider;
break;
case "category":
metadata.Category = value.Trim('"');
break;
case "tags":
metadata.Tags = value.Split(',')
.Select(t => t.Trim().Trim('"'))
.Where(t => !string.IsNullOrEmpty(t))
.ToList();
break;
case "metadata":
try
{
var metadataJson = JsonSerializer.Deserialize<Dictionary<string, object>>(
value, _jsonOptions);
foreach (var kvp in metadataJson)
metadata.Metadata[kvp.Key] = kvp.Value;
}
catch { /* Игнорируем ошибки парсинга JSON */ }
break;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error parsing directive: {Directive}", directive);
}
}
private ScriptParameter? ParseParameter(string value)
{
// Два формата: JSON и старый текстовый
if (value.TrimStart().StartsWith("{"))
{
return ParseJsonParameter(value);
}
else
{
return ParseLegacyParameter(value);
}
}
private ScriptParameter? ParseJsonParameter(string json)
{
try
{
var param = JsonSerializer.Deserialize<ScriptParameter>(json, _jsonOptions);
// Валидация обязательных полей
if (string.IsNullOrEmpty(param?.Name))
throw new ArgumentException("Parameter name is required");
if (param.Type == ParameterType.Table && string.IsNullOrEmpty(param.TableQuery))
throw new ArgumentException("TableQuery is required for Table type");
return param;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error parsing JSON parameter");
return null;
}
}
private ScriptParameter? ParseLegacyParameter(string legacy)
{
// Формат: Type Name "Display Name" default="value"
var match = Regex.Match(legacy,
@"(\w+)\s+(\w+)\s+""([^""]+)""(?:\s+default=""([^""]*)"")?(?:\s+@table\s+""([^""]+)"")?");
if (!match.Success) return null;
return new ScriptParameter
{
Type = Enum.TryParse<ParameterType>(match.Groups[1].Value, true, out var type)
? type : ParameterType.String,
Name = match.Groups[2].Value,
DisplayName = match.Groups[3].Value,
DefaultValue = match.Groups[4].Success ? match.Groups[4].Value : null,
TableQuery = match.Groups[5].Success ? match.Groups[5].Value : null
};
}
private OutputDefinition? ParseOutput(string value)
{
if (value.TrimStart().StartsWith("{"))
{
return JsonSerializer.Deserialize<OutputDefinition>(value, _jsonOptions);
}
// Старый формат: type:subtype "Description"
var match = Regex.Match(value, @"(\w+)(?::(\w+))?\s+""([^""]+)""");
if (!match.Success) return null;
return new OutputDefinition
{
Type = Enum.TryParse<OutputType>(match.Groups[1].Value, true, out var type)
? type : OutputType.Table,
SubType = match.Groups[2].Success ? match.Groups[2].Value : null,
Description = match.Groups[3].Value
};
}
private void ExtractCategoryAndTags(ScriptMetadata metadata)
{
// Извлекаем категорию из пути файла
if (string.IsNullOrEmpty(metadata.Category))
{
var relativePath = Path.GetDirectoryName(metadata.FullPath);
if (!string.IsNullOrEmpty(relativePath))
{
metadata.Category = Path.GetFileName(relativePath);
}
}
// Автоматическое добавление тегов на основе имени файла
var fileName = Path.GetFileNameWithoutExtension(metadata.FileName);
var words = fileName.Split('_', '-', ' ')
.Where(w => w.Length > 2)
.Select(w => w.ToLower());
metadata.Tags.AddRange(words);
metadata.Tags = metadata.Tags.Distinct().ToList();
}
public async Task<ScriptMetadata> ParseAsync(string filePath, CancellationToken cancellationToken = default)
{
var content = await File.ReadAllTextAsync(filePath, cancellationToken);
return Parse(filePath, content);
}
// Вспомогательные методы для многострочных комментариев
private void ProcessInlineMultilineComment(string line, ScriptMetadata metadata)
{
var content = line.Substring(2, line.IndexOf("*/") - 2).Trim();
ProcessCommentContent(content, metadata);
}
private void ProcessMultilineCommentStart(string line, ScriptMetadata metadata)
{
var content = line.Substring(2).Trim();
ProcessCommentContent(content, metadata);
}
private void ProcessMultilineCommentEnd(string line, ScriptMetadata metadata)
{
var content = line.Substring(0, line.IndexOf("*/")).Trim();
ProcessCommentContent(content, metadata);
}
private void ProcessMultilineCommentContent(string line, ScriptMetadata metadata)
{
ProcessCommentContent(line, metadata);
}
private void ProcessCommentContent(string content, ScriptMetadata metadata) // Убрали ref
{
if (content.StartsWith("@"))
{
ProcessDirective(content, metadata);
}
}
}