< Summary

Information
Class: Anichron.Infrastructure.Data.DatabaseFacadeExtensions
Assembly: Anichron.Infrastructure
File(s): /home/runner/work/anichron/anichron/src/Anichron.Infrastructure/Data/DatabaseFacadeExtensions.cs
Tag: 228_36821334185
Line coverage
0%
Covered lines: 0
Uncovered lines: 33
Coverable lines: 33
Total lines: 74
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 6
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
MigrateWithAdvisoryLockAsync()0%4260%

File(s)

/home/runner/work/anichron/anichron/src/Anichron.Infrastructure/Data/DatabaseFacadeExtensions.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.EntityFrameworkCore.Infrastructure;
 3using System.Diagnostics.CodeAnalysis;
 4
 5namespace Anichron.Infrastructure.Data;
 6
 7public static class DatabaseFacadeExtensions
 8{
 9    // S2077 flags the two interpolated pg_advisory_lock command texts below as SQL built by
 10    // string formatting. There is no injection surface: the only interpolated value is
 11    // PostgresConstants.MigrationAdvisoryLockId, a `const long` fixed at compile time, and no
 12    // caller can influence it. The rule matches the interpolation PATTERN, not a reachable taint
 13    // path, so this is a false positive rather than a finding to fix.
 14    //
 15    // Suppressed at the method rather than in .editorconfig so the justification travels with the
 16    // code a reader is looking at. It became an ERROR rather than a warning when
 17    // SonarAnalyzer.CSharp went 10.25 → 10.34 under TreatWarningsAsErrors.
 18    //
 19    // 📌 Parameterising both commands would remove the suppression and is worth doing on its own
 20    // merits — but it is a behaviour change, and this is a dependency-bump PR.
 21    [SuppressMessage(
 22        "Major Code Smell",
 23        "S2077:Formatting SQL queries is security-sensitive",
 24        Justification = "Interpolates only a compile-time const; no caller-controlled input reaches this SQL.")]
 25    public static async Task MigrateWithAdvisoryLockAsync(
 26        this DatabaseFacade database, CancellationToken ct, int maxAttempts = 30)
 027    {
 28        // Explicitly hold the connection open so that all operations — acquire lock,
 29        // migrate, release lock — run on the same PostgreSQL session. Session-level advisory
 30        // locks are tied to the session; a different connection would see a different lock.
 031        await database.OpenConnectionAsync(ct);
 32        try
 033        {
 034            var conn = database.GetDbConnection();
 35
 036            for (var attempt = 1; attempt <= maxAttempts; attempt++)
 037            {
 38                bool acquired;
 039                await using (var tryLockCmd = conn.CreateCommand())
 040                {
 041                    tryLockCmd.CommandText =
 042                        $"SELECT pg_try_advisory_lock({PostgresConstants.MigrationAdvisoryLockId})";
 043                    acquired = (bool)(await tryLockCmd.ExecuteScalarAsync(ct))!;
 044                }
 45
 046                if (acquired)
 047                {
 48                    try
 049                    {
 050                        await database.MigrateAsync(ct);
 051                        return;
 52                    }
 53                    finally
 054                    {
 055                        await using var unlockCmd = conn.CreateCommand();
 056                        unlockCmd.CommandText =
 057                            $"SELECT pg_advisory_unlock({PostgresConstants.MigrationAdvisoryLockId})";
 058                        await unlockCmd.ExecuteNonQueryAsync(ct);
 059                    }
 060                }
 61
 062                if (attempt < maxAttempts)
 063                    await Task.Delay(TimeSpan.FromSeconds(1), ct);
 064            }
 65
 066            throw new TimeoutException(
 067                $"Could not acquire the migration advisory lock after {maxAttempts} attempts.");
 68        }
 69        finally
 070        {
 071            await database.CloseConnectionAsync();
 072        }
 073    }
 74}

Methods/Properties

MigrateWithAdvisoryLockAsync()