using Bit.Seeder.Migration.Models;
using Bit.Seeder.Migration.Utils;
using Microsoft.Extensions.Logging;
using MySqlConnector;
namespace Bit.Seeder.Migration.Databases;
///
/// MariaDB database importer that handles schema creation, data import, and foreign key management.
///
public class MariaDbImporter(DatabaseConfig config, ILogger logger) : IDatabaseImporter
{
private readonly ILogger _logger = logger;
private readonly string _host = config.Host;
private readonly int _port = config.Port > 0 ? config.Port : 3306;
private readonly string _database = config.Database;
private readonly string _username = config.Username;
private readonly string _password = config.Password;
private MySqlConnection? _connection;
private bool _disposed = false;
///
/// Connects to the MariaDB database.
///
public bool Connect()
{
try
{
// Build connection string with redacted password for safe logging
var safeConnectionString = $"Server={_host};Port={_port};Database={_database};" +
$"Uid={_username};Pwd={DbSeederConstants.REDACTED_PASSWORD};" +
$"ConnectionTimeout={DbSeederConstants.DEFAULT_CONNECTION_TIMEOUT};" +
$"CharSet=utf8mb4;AllowLoadLocalInfile=false;MaxPoolSize={DbSeederConstants.DEFAULT_MAX_POOL_SIZE};";
var actualConnectionString = safeConnectionString.Replace(DbSeederConstants.REDACTED_PASSWORD, _password);
_connection = new MySqlConnection(actualConnectionString);
_connection.Open();
_logger.LogInformation("Connected to MariaDB: {Host}:{Port}/{Database}", _host, _port, _database);
return true;
}
catch (Exception ex)
{
_logger.LogError("Failed to connect to MariaDB: {Message}", ex.Message);
return false;
}
}
public void Disconnect()
{
if (_connection != null)
{
_connection.Close();
_connection.Dispose();
_connection = null;
_logger.LogInformation("Disconnected from MariaDB");
}
}
///
/// Creates a table in MariaDB from the provided schema definition.
///
public bool CreateTableFromSchema(
string tableName,
List columns,
Dictionary columnTypes,
List? specialColumns = null)
{
specialColumns ??= [];
if (_connection == null)
throw new InvalidOperationException("Not connected to database");
IdentifierValidator.ValidateOrThrow(tableName, "table name");
try
{
var mariaColumns = new List();
foreach (var colName in columns)
{
IdentifierValidator.ValidateOrThrow(colName, "column name");
var sqlServerType = columnTypes.GetValueOrDefault(colName, "VARCHAR(MAX)");
var mariaType = ConvertSqlServerTypeToMariaDB(sqlServerType, specialColumns.Contains(colName));
mariaColumns.Add($"`{colName}` {mariaType}");
}
var createSql = $@"
CREATE TABLE IF NOT EXISTS `{tableName}` (
{string.Join(",\n ", mariaColumns)}
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
_logger.LogInformation("Creating table {TableName} in MariaDB", tableName);
_logger.LogDebug("CREATE TABLE SQL: {CreateSql}", createSql);
using var command = new MySqlCommand(createSql, _connection);
command.ExecuteNonQuery();
_logger.LogInformation("Successfully created table {TableName}", tableName);
return true;
}
catch (Exception ex)
{
_logger.LogError("Error creating table {TableName}: {Message}", tableName, ex.Message);
return false;
}
}
public List GetTableColumns(string tableName)
{
if (_connection == null)
throw new InvalidOperationException("Not connected to database");
try
{
var query = @"
SELECT column_name
FROM information_schema.columns
WHERE table_name = @tableName AND table_schema = @database
ORDER BY ordinal_position";
using var command = new MySqlCommand(query, _connection);
command.Parameters.AddWithValue("@tableName", tableName);
command.Parameters.AddWithValue("@database", _database);
var columns = new List();
using var reader = command.ExecuteReader();
while (reader.Read())
{
var colName = reader.GetString(0);
// Validate column name immediately to prevent second-order SQL injection
IdentifierValidator.ValidateOrThrow(colName, "column name");
columns.Add(colName);
}
return columns;
}
catch (Exception ex)
{
_logger.LogError("Error getting columns for table {TableName}: {Message}", tableName, ex.Message);
return [];
}
}
///
/// Imports data into a MariaDB table using batched INSERT statements.
///
public bool ImportData(
string tableName,
List columns,
List