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 rootConn, string templateDbName, Func? seedAsync = null) { await using var root = new MySqlConnection(rootConn); await root.OpenAsync(); await ExecAsync(root, $"DROP DATABASE IF EXISTS `{templateDbName}`;"); await ExecAsync(root, $"CREATE DATABASE `{templateDbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); // Run EF once to build schema and seed var templateConn = new MySqlConnectionStringBuilder(rootConn) { Database = templateDbName }.ConnectionString; await using var ctx = AppDb(templateConn); await ctx.Database.EnsureCreatedAsync(); if (seedAsync != null) await seedAsync(ctx); } public static async Task CloneFromTemplateAsync( string rootConn, string templateDbName, string newDbName, Func? seed = null) { var newConnStr = new MySqlConnectionStringBuilder(rootConn) { Database = newDbName }.ConnectionString; await using var root = new MySqlConnection(rootConn); await root.OpenAsync(); // Create target DB await ExecAsync(root, $"CREATE DATABASE `{newDbName}` 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 `{newDbName}`; {createTable};"); } // 2) Copy data foreach (var table in tables) { var sql = $"INSERT INTO `{newDbName}`.`{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;"); // Ready-to-use EF context for the cloned DB //return AppDb(newConnStr); AppDbContext dbContext = AppDb(newConnStr); if (seed != null) await seed(dbContext); return dbContext; } private static AppDbContext AppDb(string connStr) { var opts = new DbContextOptionsBuilder() .UseMySql(connStr, ServerVersion.AutoDetect(connStr), o => o.EnableRetryOnFailure()) .EnableSensitiveDataLogging() .Options; return new AppDbContext(opts); } 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(); } }