using System.Data.Common; using System.Text.Json; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using ScadaLink.Commons.Entities.ExternalSystems; using ScadaLink.Commons.Interfaces.Repositories; using ScadaLink.Commons.Interfaces.Services; using ScadaLink.Commons.Types.Enums; using ScadaLink.StoreAndForward; namespace ScadaLink.ExternalSystemGateway; /// /// WP-9: Database access from scripts. /// Database.Connection("name") — returns ADO.NET SqlConnection (connection pooling). /// Database.CachedWrite("name", "sql", params) — submits to S&F engine. /// public class DatabaseGateway : IDatabaseGateway { private readonly IExternalSystemRepository _repository; private readonly StoreAndForwardService? _storeAndForward; private readonly ILogger _logger; public DatabaseGateway( IExternalSystemRepository repository, ILogger logger, StoreAndForwardService? storeAndForward = null) { _repository = repository; _logger = logger; _storeAndForward = storeAndForward; } /// /// Returns an open SqlConnection from the named database connection definition. /// Connection pooling is managed by the underlying ADO.NET provider. /// public async Task GetConnectionAsync( string connectionName, CancellationToken cancellationToken = default) { var definition = await ResolveConnectionAsync(connectionName, cancellationToken); if (definition == null) { throw new InvalidOperationException($"Database connection '{connectionName}' not found"); } var connection = new SqlConnection(definition.ConnectionString); await connection.OpenAsync(cancellationToken); return connection; } /// /// Submits a SQL write to the store-and-forward engine for reliable delivery. /// public async Task CachedWriteAsync( string connectionName, string sql, IReadOnlyDictionary? parameters = null, string? originInstanceName = null, CancellationToken cancellationToken = default) { var definition = await ResolveConnectionAsync(connectionName, cancellationToken); if (definition == null) { throw new InvalidOperationException($"Database connection '{connectionName}' not found"); } if (_storeAndForward == null) { throw new InvalidOperationException("Store-and-forward service not available for cached writes"); } var payload = JsonSerializer.Serialize(new { ConnectionName = connectionName, Sql = sql, Parameters = parameters }); await _storeAndForward.EnqueueAsync( StoreAndForwardCategory.CachedDbWrite, connectionName, payload, originInstanceName, definition.MaxRetries > 0 ? definition.MaxRetries : null, definition.RetryDelay > TimeSpan.Zero ? definition.RetryDelay : null); } private async Task ResolveConnectionAsync( string connectionName, CancellationToken cancellationToken) { var connections = await _repository.GetAllDatabaseConnectionsAsync(cancellationToken); return connections.FirstOrDefault(c => c.Name.Equals(connectionName, StringComparison.OrdinalIgnoreCase)); } }