Centralize Postgres connection string policy across all modules
Extract connection string building into PostgresConnectionStringPolicy so all services use consistent pooling, application_name, and timeout settings. Adopt the new policy in 20+ module DataSource/ServiceCollectionExtensions classes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ namespace StellaOps.Scanner.Reachability.Cache;
|
||||
/// </summary>
|
||||
public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly Services.PostgresReachabilityDataSourceProvider _dataSourceProvider;
|
||||
private readonly ILogger<PostgresReachabilityCache> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
@@ -28,8 +28,16 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
string connectionString,
|
||||
ILogger<PostgresReachabilityCache> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
: this(new Services.PostgresReachabilityDataSourceProvider(connectionString), logger, timeProvider)
|
||||
{
|
||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
}
|
||||
|
||||
internal PostgresReachabilityCache(
|
||||
Services.PostgresReachabilityDataSourceProvider dataSourceProvider,
|
||||
ILogger<PostgresReachabilityCache> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSourceProvider = dataSourceProvider ?? throw new ArgumentNullException(nameof(dataSourceProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
@@ -40,8 +48,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
string graphHash,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync(cancellationToken);
|
||||
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Get cache entry
|
||||
const string entrySql = """
|
||||
@@ -120,8 +127,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entry);
|
||||
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync(cancellationToken);
|
||||
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var tx = await conn.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -202,8 +208,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
string sinkMethodKey,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync(cancellationToken);
|
||||
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
const string sql = """
|
||||
SELECT p.is_reachable, p.path_length, p.confidence, p.computed_at
|
||||
@@ -246,8 +251,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
IEnumerable<string> affectedMethodKeys,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync(cancellationToken);
|
||||
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// For now, invalidate entire cache for service
|
||||
// More granular invalidation would require additional indices
|
||||
@@ -283,8 +287,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
string serviceId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync(cancellationToken);
|
||||
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
const string sql = """
|
||||
SELECT total_hits, total_misses, full_recomputes, incremental_computes,
|
||||
@@ -392,4 +395,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private ValueTask<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||
=> _dataSourceProvider.OpenConnectionAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,12 @@ public static class ServiceCollectionExtensions
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||
|
||||
services.TryAddSingleton(_ => new PostgresReachabilityDataSourceProvider(connectionString));
|
||||
|
||||
// CVE-Symbol Mapping Service
|
||||
services.TryAddSingleton<ICveSymbolMappingService>(sp =>
|
||||
new PostgresCveSymbolMappingRepository(
|
||||
connectionString,
|
||||
sp.GetRequiredService<PostgresReachabilityDataSourceProvider>(),
|
||||
sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<PostgresCveSymbolMappingRepository>>()));
|
||||
|
||||
// Stack Evaluator (already exists, ensure registered)
|
||||
|
||||
@@ -15,14 +15,21 @@ namespace StellaOps.Scanner.Reachability.Services;
|
||||
/// </summary>
|
||||
public sealed class PostgresCveSymbolMappingRepository : ICveSymbolMappingService
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly PostgresReachabilityDataSourceProvider _dataSourceProvider;
|
||||
private readonly ILogger<PostgresCveSymbolMappingRepository> _logger;
|
||||
|
||||
public PostgresCveSymbolMappingRepository(
|
||||
string connectionString,
|
||||
ILogger<PostgresCveSymbolMappingRepository> logger)
|
||||
: this(new PostgresReachabilityDataSourceProvider(connectionString), logger)
|
||||
{
|
||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
}
|
||||
|
||||
internal PostgresCveSymbolMappingRepository(
|
||||
PostgresReachabilityDataSourceProvider dataSourceProvider,
|
||||
ILogger<PostgresCveSymbolMappingRepository> logger)
|
||||
{
|
||||
_dataSourceProvider = dataSourceProvider ?? throw new ArgumentNullException(nameof(dataSourceProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -206,9 +213,7 @@ public sealed class PostgresCveSymbolMappingRepository : ICveSymbolMappingServic
|
||||
|
||||
private async Task<NpgsqlConnection> OpenConnectionAsync(CancellationToken ct)
|
||||
{
|
||||
var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync(ct);
|
||||
return conn;
|
||||
return await _dataSourceProvider.OpenConnectionAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static CveSinkMapping MapFromReader(NpgsqlDataReader reader)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Npgsql;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Services;
|
||||
|
||||
internal sealed class PostgresReachabilityDataSourceProvider : IAsyncDisposable
|
||||
{
|
||||
private readonly Lazy<NpgsqlDataSource> _dataSource;
|
||||
|
||||
public PostgresReachabilityDataSourceProvider(string connectionString)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||
_dataSource = new Lazy<NpgsqlDataSource>(
|
||||
() => CreateDataSource(connectionString),
|
||||
isThreadSafe: true);
|
||||
}
|
||||
|
||||
public ValueTask<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||
=> _dataSource.Value.OpenConnectionAsync(cancellationToken);
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_dataSource.IsValueCreated)
|
||||
{
|
||||
await _dataSource.Value.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||
{
|
||||
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||
{
|
||||
ApplicationName = "stellaops-scanner-reachability",
|
||||
};
|
||||
|
||||
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: added a named reachability datasource provider and routed PostgreSQL-backed reachability services through it. |
|
||||
| QA-SCANNER-VERIFY-005 | DONE | SPRINT_20260212_002 run-001: `api-gateway-boundary-extractor` passed Tier 0/1/2 and moved to `docs/features/checked/scanner/`. |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Libraries/StellaOps.Scanner.Reachability/StellaOps.Scanner.Reachability.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
Reference in New Issue
Block a user