using System.Text.Json; using AnalyticsDAL.CustomCode.Analysis; using AnalyticsDAL.CustomCode.AnalysisWorkspace; using AnalyticsDAL.CustomCode.DataSource; using AnalyticsDAL.DTO.AnalysisWorkspace; using AnalyticsDAL.DTO.DataSource; using AnalyticsDAL.DTO.ExecutionRouting; using GB5Shared.DTO.Framework.Login; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using SwBLL.ClientDatabase; using SwBLL.DbServer; using SwBLL.Provisioning; using SwDAL.Enums; namespace AnalyticsBLL.ExecutionRouting { // Decides whether AnalysisDAL.DynamicOutput should execute a report locally (the default, // used by the overwhelming majority of analyses — no workspace binding, no override) or // against a SqlWorkbench-managed external client database (MANALYSISWORKSPACE.OverrideDataSourceId // -> MDATASOURCE.SwClientDatabaseId -> MSWCLIENTDATABASE/MSWDBSERVER). Mirrors the resolution // shape already used by AnalyticsBLL.DatasetResolver.DatasetResolver.ResolveAnalysisQueryAsync, // but entered from an AnalysisId (the legacy DynamicOutput entrypoint has no BICatalogId). public class AnalysisExecutionRouteResolver : IAnalysisExecutionRouteResolver { private readonly IAnalysisDAL _analysisDAL; private readonly IAnalysisWorkspaceDAL _analysisWorkspaceDAL; private readonly IDataSourceDAL _dataSourceDAL; private readonly IClientDatabaseBLL _clientDatabaseBLL; private readonly IDbServerBLL _dbServerBLL; private readonly ITargetDbExecutor _targetDbExecutor; private readonly ILogger _logger; public AnalysisExecutionRouteResolver( IAnalysisDAL analysisDAL, IAnalysisWorkspaceDAL analysisWorkspaceDAL, IDataSourceDAL dataSourceDAL, IClientDatabaseBLL clientDatabaseBLL, IDbServerBLL dbServerBLL, ITargetDbExecutor targetDbExecutor, ILogger logger) { _analysisDAL = analysisDAL; _analysisWorkspaceDAL = analysisWorkspaceDAL; _dataSourceDAL = dataSourceDAL; _clientDatabaseBLL = clientDatabaseBLL; _dbServerBLL = dbServerBLL; _targetDbExecutor = targetDbExecutor; _logger = logger; } public async Task ResolveAsync(int analysisId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("resolve-execution-route", new { analysisId }); var local = new ExecutionRouteDTO { IsExternal = false }; var analysis = await _analysisDAL.GetAnalysisHeader(analysisId, login, ct).ConfigureAwait(false); if (analysis is null || analysis.WorkspaceId == -1) return local; var bindingsJson = await _analysisWorkspaceDAL.GetWorkspacesByAnalysis(analysisId, login, ct).ConfigureAwait(false); var bindings = JsonSerializer.Deserialize>(bindingsJson) ?? new List(); var overrideDataSourceId = bindings .FirstOrDefault(b => b.WorkspaceId == analysis.WorkspaceId)?.OverrideDataSourceId; if (overrideDataSourceId is null) return local; var dataSourceJson = await _dataSourceDAL.GetDataSource(overrideDataSourceId.Value, login, ct).ConfigureAwait(false); var dataSource = JsonSerializer.Deserialize(dataSourceJson); if (dataSource?.SwClientDatabaseId is null) return local; var clientDatabase = await _clientDatabaseBLL.GetById(dataSource.SwClientDatabaseId.Value, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException( $"Analysis {analysisId}'s linked SwClientDatabaseId {dataSource.SwClientDatabaseId} does not exist."); var dbServer = await _dbServerBLL.GetById(clientDatabase.DbServerId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException( $"Analysis {analysisId}'s linked client database's DbServerId {clientDatabase.DbServerId} does not exist."); if (dbServer.DbType != (byte)DbType.SqlServer) throw new InvalidOperationException( $"Analysis {analysisId}'s linked client database uses DbType={dbServer.DbType}, which is not yet " + "supported for report execution (SQL Server only in this version)."); var connectionString = await _targetDbExecutor .BuildConnectionStringForRoleAsync(dbServer, clientDatabase, ClientDbLoginRole.ReadOnly, ct) .ConfigureAwait(false); return new ExecutionRouteDTO { IsExternal = true, ConnectionString = connectionString }; } catch (Exception ex) { GB5Trace.MarkFailed("resolve-execution-route-failed", ex); _logger.LogError(ex, "ResolveAsync failed for AnalysisId {Id}", analysisId); throw; } } } }