using JSMR.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using MySqlConnector; using System.Data; namespace JSMR.Tests.Fixtures; public static class MariaDbClone { public static async Task CreateTemplateAsync( string rootConnectionString, string templateDbName, Func? seedAsync = null) { await using MySqlConnection root = new(rootConnectionString); await root.OpenAsync(); await ExecAsync(root, $"DROP DATABASE IF EXISTS `{templateDbName}`;"); await ExecAsync(root, $"CREATE DATABASE `{templateDbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); var templateConn = new MySqlConnectionStringBuilder(rootConnectionString) { Database = templateDbName }.ConnectionString; await using var ctx = CreateAppDbContext(templateConn); await ctx.Database.EnsureCreatedAsync(); if (seedAsync != null) await seedAsync(ctx); } public static async Task CloneFromTemplateAsync( string rootConnectionString, string templateDbName, Func? seed = null) { var databaseName = $"t_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}"; var connectionString = new MySqlConnectionStringBuilder(rootConnectionString) { Database = databaseName }.ConnectionString; await using var root = new MySqlConnection(rootConnectionString); await root.OpenAsync(); // Create target DB await ExecAsync(root, $"CREATE DATABASE `{databaseName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); // Disable FK checks while recreating schema & loading data await ExecAsync(root, $"SET SESSION sql_log_bin = 0;"); // avoid binlog noise (optional) await ExecAsync(root, $"SET SESSION foreign_key_checks = 0;"); // 1) Create tables using exact DDL from templatedb var tables = await GetTableNamesAsync(root, templateDbName); foreach (var table in tables) { var createTable = await ShowCreateTableAsync(root, templateDbName, table); // Run DDL in the new DB (the CREATE statement itself doesn't include db name) await ExecAsync(root, $"USE `{databaseName}`; {createTable};"); } //await Parallel.ForEachAsync(tables, async (table, cancellationToken) => //{ // var createTable = await ShowCreateTableAsync(root, templateDbName, table); // // Run DDL in the new DB (the CREATE statement itself doesn't include db name) // await ExecAsync(root, $"USE `{databaseName}`; {createTable};"); //}); // 2) Copy data foreach (var table in tables) { var sql = $"INSERT INTO `{databaseName}`.`{table}` SELECT * FROM `{templateDbName}`.`{table}`;"; await ExecAsync(root, sql); } await ExecAsync(root, $"SET SESSION foreign_key_checks = 1;"); await ExecAsync(root, $"SET SESSION sql_log_bin = 1;"); AppDbContext dbContext = CreateAppDbContext(connectionString); if (seed != null) await seed(dbContext); return dbContext; } private static AppDbContext CreateAppDbContext(string connectionString) { var options = new DbContextOptionsBuilder() .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), o => o.EnableRetryOnFailure()) .EnableSensitiveDataLogging() .Options; return new AppDbContext(options); } private static async Task> GetTableNamesAsync(MySqlConnection conn, string db) { const string sql = @" SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = @db AND TABLE_TYPE = 'BASE TABLE';"; var result = new List(); await using var cmd = new MySqlCommand(sql, conn) { Parameters = { new("@db", db) } }; await using var rdr = await cmd.ExecuteReaderAsync(); while (await rdr.ReadAsync()) result.Add(rdr.GetString(0)); return result; } private static async Task ShowCreateTableAsync(MySqlConnection conn, string db, string table) { await using var cmd = new MySqlCommand($"SHOW CREATE TABLE `{db}`.`{table}`;", conn); await using var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow); if (!await rdr.ReadAsync()) throw new InvalidOperationException($"SHOW CREATE TABLE returned no rows for {db}.{table}"); // Column 1: table name, Column 2: CREATE TABLE DDL var ddl = rdr.GetString(1); // Ensure the statement ends with ';' if (!ddl.TrimEnd().EndsWith(";")) ddl += ";"; return ddl; } private static async Task ExecAsync(MySqlConnection conn, string sql) { await using var cmd = new MySqlCommand(sql, conn) { CommandTimeout = 0 }; await cmd.ExecuteNonQueryAsync(); } }