using System; using System.IO; using System.Text.RegularExpressions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Localization; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Localization; using Volo.Abp.Domain.Services; namespace Sozsoft.Mcp.Services; /// /// Ekranin veri kaynagi olan view, procedure ve fonksiyon scriptlerini SQL Query Manager'in yazdigi /// yere (sql/object/{Ad}.sql) ve PostgreSQL esine (postgres/object/{Ad}.sql) yazar. /// Govde diyalektler arasinda guvenle cevrilemedigi icin () iki /// script de istemciden gelir; biri olmadan yazilmaz. /// /// Veritabaninda calistirilmaz: nesneyi, tablo gibi DB Migrate kurar (SqlDataSeeder). Nesnenin /// adi SQL Server scriptinin basligindan okunur; basliklar tekrar calismaya dayanikli bicime cevrilir /// (CREATE OR ALTER / CREATE OR REPLACE), ayni adla tekrar yazmak scripti gunceller. /// /// public interface IMcpSqlObjectWriter { McpOutcome Save(string sqlServerScript, string postgreSqlScript); } public partial class McpSqlObjectWriter( IConfiguration configuration, IStringLocalizer localizer) : DomainService, IMcpSqlObjectWriter { private const string SqlServerProvider = "SQL Server"; private const string PostgreSqlProvider = "PostgreSQL"; public McpOutcome Save(string sqlServerScript, string postgreSqlScript) { var sqlServer = Canonicalize(sqlServerScript, SqlServerHeaderRegex(), "CREATE OR ALTER"); if (sqlServer is null) return McpProblem.From(localizer, McpErrorCodes.SqlObjectInvalid, SqlServerProvider); var postgreSql = Canonicalize(postgreSqlScript, PostgreSqlHeaderRegex(), "CREATE OR REPLACE"); if (postgreSql is null || !string.Equals(sqlServer.Name, postgreSql.Name, StringComparison.OrdinalIgnoreCase)) return McpProblem.From(localizer, McpErrorCodes.SqlObjectInvalid, PostgreSqlProvider); // Tablo scriptinin yerine view yazilirsa migrate tabloyu bir daha kuramazdi. if (PostgreSqlTableScript.ReadSeedColumns(configuration, sqlServer.Name).Count > 0) return McpProblem.From(localizer, McpErrorCodes.TableAlreadyExists, sqlServer.Name); var fileName = $"{sqlServer.Name}.sql"; var sqlServerPath = Write(SeedPathResolver.SqlFolder, fileName, sqlServer.Script); var postgreSqlPath = Write(SeedPathResolver.PostgresFolder, fileName, postgreSql.Script); return new McpSavedSqlObject(sqlServer.Name, sqlServer.Kind, sqlServerPath, postgreSqlPath); } /// Scripti saglayicinin object klasorune yazar; seed kokune goreli yolu dondurur. private string Write(string providerFolder, string fileName, string script) { var folder = SeedPathResolver.GetPath(configuration, providerFolder, SeedPathResolver.ObjectFolder); Directory.CreateDirectory(folder); File.WriteAllText(Path.Combine(folder, fileName), script); return $"{providerFolder}/{SeedPathResolver.ObjectFolder}/{fileName}"; } /// /// Script tek bir nesne tanimlamalidir; basligi tekrar calismaya dayanikli bicime cevrilir. /// Yorumlar yalnizca aramada yok sayilir, dosyada kalir. Tek nesne tanimlamayan script icin null. /// private static CanonicalScript? Canonicalize(string script, Regex headerRegex, string createClause) { var headers = string.IsNullOrWhiteSpace(script) ? null : headerRegex.Matches(CommentRegex().Replace(script, " ")); if (headers is not { Count: 1 }) return null; var name = headers[0].Groups["name"].Value; var kind = headers[0].Groups["kind"].Value.ToUpperInvariant() switch { "PROC" => "PROCEDURE", var value => value }; // Yorumdaki ornek baslik degismesin diye yalnizca nesnenin adini tasiyan ilk baslik cevrilir. var replaced = false; var canonical = headerRegex.Replace(script.Trim(), m => { if (replaced || !string.Equals(m.Groups["name"].Value, name, StringComparison.OrdinalIgnoreCase)) return m.Value; replaced = true; return $"{createClause} {kind} {m.Groups["qualified"].Value}"; }); return new CanonicalScript(canonical + Environment.NewLine, name, kind); } private sealed record CanonicalScript(string Script, string Name, string Kind); [GeneratedRegex(@"/\*.*?\*/|--[^\r\n]*", RegexOptions.Singleline)] private static partial Regex CommentRegex(); [GeneratedRegex(@"\b(?:CREATE\s+OR\s+ALTER|CREATE|ALTER)\s+(?VIEW|PROCEDURE|PROC|FUNCTION)\s+(?(?:\[?dbo\]?\s*\.\s*)?\[?(?[A-Za-z_][A-Za-z0-9_]*)\]?)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex SqlServerHeaderRegex(); [GeneratedRegex(@"\bCREATE\s+(?:OR\s+REPLACE\s+)?(?VIEW|PROCEDURE|FUNCTION)\s+(?(?:""?public""?\s*\.\s*)?""?(?[A-Za-z_][A-Za-z0-9_]*)""?)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex PostgreSqlHeaderRegex(); } /// VIEW, PROCEDURE ya da FUNCTION. /// Seed kokune gore goreli yol. /// Seed kokune gore goreli yol. public sealed record McpSavedSqlObject(string Name, string Kind, string SqlServerPath, string PostgreSqlPath);