using Dapper;
using GB5Shared.Connection;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.QueryExecutor;
using Microsoft.Data.SqlClient;
using Npgsql;
using System.Data;
using TCMSDAL.DTO;
using TCMSDAL.Interfaces;
using TCMSDAL.QueryBuilders;
using static GB5Shared.GB5Constant.Constant;
namespace TCMSDAL.Implementations;
///
/// DAL for Test Environment Management.
/// Dual-connection pattern:
/// - gb5system (MSERVERCONFIG, MSERVER): accessed via IApplicationConnection.Gb5SystemConnectionString() + direct Dapper.
/// - TCMS DB (TTESTENVRESETLOG): accessed via IQueryExecutor with LoginDTO.
/// - Target run-DB server (DDL): accessed via caller-supplied connection string.
/// DB type is detected from the connection string prefix (Host= → PostgreSQL, otherwise SQL Server).
///
public class TCMSTestEnvDAL : ITCMSTestEnvDAL
{
private readonly IQueryExecutor _Qe;
private readonly IApplicationConnection _AppConn;
public TCMSTestEnvDAL(
IQueryExecutor queryExecutor,
IApplicationConnection appConn)
{
_Qe = queryExecutor;
_AppConn = appConn;
}
// ── gb5system helpers ────────────────────────────────────────────────────
private async Task SystemConnAsync(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
string cs = await _AppConn.Gb5SystemConnectionString().ConfigureAwait(false);
return IsPostgres(cs) ? new NpgsqlConnection(cs) : new SqlConnection(cs);
}
private static bool IsPostgres(string connectionString)
=> connectionString.Contains("Host=", StringComparison.OrdinalIgnoreCase);
// ── MSERVERCONFIG list ────────────────────────────────────────────────────
public async Task> GetTestEnvironmentsAsync(
LoginDTO login, CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
return await conn.QueryAsync(
TCMSTestEnvQB.GET_TEST_ENVIRONMENTS,
new { ClientId = login.ClientId }).ConfigureAwait(false)
?? Enumerable.Empty();
}
public async Task GetTestEnvByIdAsync(
int serverConfigId, CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
return await conn.QueryFirstOrDefaultAsync(
TCMSTestEnvQB.GET_TEST_ENV_BY_ID,
new { ServerConfigId = serverConfigId }).ConfigureAwait(false);
}
public async Task GetTestDbConnectionRawAsync(
int serverConfigId, CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
return await conn.QueryFirstOrDefaultAsync(
TCMSTestEnvQB.GET_TEST_DB_CONNECTION_RAW,
new { ServerConfigId = serverConfigId }).ConfigureAwait(false);
}
public async Task ConnectionNameExistsAsync(
string connectionName, CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
var count = await conn.ExecuteScalarAsync(
TCMSTestEnvQB.CHECK_CONNECTIONNAME_EXISTS,
new { ConnectionName = connectionName }).ConfigureAwait(false);
return count > 0;
}
public async Task RegisterTestDbAsync(
TCMSRegisterTestDbDTO dto, LoginDTO login, CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
return await conn.ExecuteScalarAsync(
TCMSTestEnvQB.INSERT_MSERVERCONFIG,
new
{
dto.ConnectionName, dto.ServerId, DatabaseType = dto.DatabaseType,
dto.DatabaseName, dto.DatabaseUserName, dto.DatabasePassword,
Port = dto.Port, dto.ControlSourceDbName,
dto.ClientId, dto.ClientSiteId,
CreatedById = login.UserId,
}).ConfigureAwait(false);
}
public async Task DeregisterTestDbAsync(
int serverConfigId, LoginDTO login, CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
return await conn.ExecuteAsync(
TCMSTestEnvQB.DEREGISTER_TEST_DB,
new { ServerConfigId = serverConfigId, ModifiedById = login.UserId }).ConfigureAwait(false);
}
public async Task> GetMServerSelectListAsync(
CancellationToken ct)
{
using var conn = await SystemConnAsync(ct).ConfigureAwait(false);
return await conn.QueryAsync(
TCMSTestEnvQB.GET_MSERVER_SELECT_LIST).ConfigureAwait(false)
?? Enumerable.Empty();
}
// ── TTESTENVRESETLOG (TCMS DB via IQueryExecutor) ────────────────────────
public async Task InsertResetLogAsync(
TCMSTestEnvResetLogDTO log, LoginDTO login, CancellationToken ct)
=> await _Qe.ExecuteScalarAsync(
login,
TCMSTestEnvQB.INSERT_RESET_LOG,
new
{
log.ServerConfigId, log.ConnectionName, log.ControlSource,
log.TriggeredBy, log.DurationMs,
Success = log.Success ? 1 : 0,
log.ErrorMessage,
},
cancellationToken: ct).ConfigureAwait(false);
public async Task UpdateResetLogOutcomeAsync(
int logId, int durationMs, bool success, string? errorMessage,
LoginDTO login, CancellationToken ct)
=> await _Qe.ExecuteAsync(
login,
TCMSTestEnvQB.UPDATE_RESET_LOG_OUTCOME,
new
{
ResetLogId = logId,
DurationMs = durationMs,
Success = success ? 1 : 0,
ErrorMessage = errorMessage,
},
cancellationToken: ct).ConfigureAwait(false);
public async Task> GetResetHistoryAsync(
int serverConfigId, LoginDTO login, CancellationToken ct)
=> await _Qe.QueryAsync(
login,
TCMSTestEnvQB.GET_RESET_HISTORY,
new { ServerConfigId = serverConfigId },
cancellationToken: ct).ConfigureAwait(false)
?? Enumerable.Empty();
// ── DDL on target run-DB server ───────────────────────────────────────────
public async Task ExecuteDdlOnTargetServerAsync(
string connectionString, string ddlSql, int dbType, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
using IDbConnection conn = dbType == DBTYPE.POSTGRESQL
? new NpgsqlConnection(connectionString)
: new SqlConnection(connectionString);
// DDL timeout: 10 minutes — clone/restore operations can be long-running
await conn.ExecuteAsync(ddlSql, commandTimeout: 600).ConfigureAwait(false);
}
// ── Snapshot catalog ──────────────────────────────────────────────────────
public async Task> GetSnapshotsAsync(
string adminConnectionString, string runDbName, int dbType, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
using IDbConnection conn = dbType == DBTYPE.POSTGRESQL
? new NpgsqlConnection(adminConnectionString)
: new SqlConnection(adminConnectionString);
string sql = dbType == DBTYPE.POSTGRESQL
? @"SELECT datname AS SnapshotName,
@RunDbName AS SourceDatabase,
(SELECT pg_stat_file('base/'|| oid ||'/PG_VERSION')).modification
AS CreateDate,
pg_database_size(datname)/1048576 AS SizeMb
FROM pg_database
WHERE datname LIKE @Prefix"
: @"SELECT d.name AS SnapshotName,
DB_NAME(d.source_database_id) AS SourceDatabase,
d.create_date AS CreateDate,
SUM(mf.size) * 8 / 1024 AS SizeMb
FROM sys.databases d
JOIN sys.master_files mf ON mf.database_id = d.database_id
WHERE d.source_database_id = DB_ID(@RunDbName)
GROUP BY d.name, d.source_database_id, d.create_date";
var param = dbType == DBTYPE.POSTGRESQL
? (object)new { RunDbName = runDbName, Prefix = $"{runDbName}_snap_%" }
: new { RunDbName = runDbName };
return await conn.QueryAsync(sql, param)
.ConfigureAwait(false)
?? Enumerable.Empty();
}
}