129 lines
4.9 KiB
C#
129 lines
4.9 KiB
C#
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<AppDbContext, Task>? 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<AppDbContext> CloneFromTemplateAsync(
|
|
string rootConnectionString,
|
|
string templateDbName,
|
|
Func<AppDbContext, Task>? 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<AppDbContext>()
|
|
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), o => o.EnableRetryOnFailure())
|
|
.EnableSensitiveDataLogging()
|
|
.Options;
|
|
|
|
return new AppDbContext(options);
|
|
}
|
|
|
|
private static async Task<List<string>> 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<string>();
|
|
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<string> 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();
|
|
}
|
|
} |