using System.Reflection;
namespace StellaOps.Infrastructure.Postgres.Migrations;
///
/// Interface for running database migrations.
///
public interface IMigrationRunner
{
///
/// Gets the schema name for this migration runner.
///
string SchemaName { get; }
///
/// Gets the module name for this migration runner.
///
string ModuleName { get; }
///
/// Runs pending migrations from the specified path.
///
/// Path to directory containing SQL migration files.
/// Migration execution options.
/// Cancellation token.
/// Result of migration execution.
Task RunAsync(
string migrationsPath,
MigrationRunOptions? options = null,
CancellationToken cancellationToken = default);
///
/// Runs pending migrations from embedded resources in an assembly.
///
/// Assembly containing embedded migration resources.
/// Optional prefix to filter resources.
/// Migration execution options.
/// Cancellation token.
/// Result of migration execution.
Task RunFromAssemblyAsync(
Assembly assembly,
string? resourcePrefix = null,
MigrationRunOptions? options = null,
CancellationToken cancellationToken = default);
///
/// Gets the current migration version (latest applied migration).
///
Task GetCurrentVersionAsync(CancellationToken cancellationToken = default);
///
/// Gets all applied migrations.
///
Task> GetAppliedMigrationInfoAsync(CancellationToken cancellationToken = default);
///
/// Validates checksums of applied migrations against source files.
///
/// Assembly containing embedded migration resources.
/// Optional prefix to filter resources.
/// Cancellation token.
/// List of checksum validation errors, empty if all valid.
Task> ValidateChecksumsAsync(
Assembly assembly,
string? resourcePrefix = null,
CancellationToken cancellationToken = default);
}
///
/// Options for migration execution.
///
public sealed class MigrationRunOptions
{
///
/// Filter migrations by category. If null, all categories are included.
///
public MigrationCategory? CategoryFilter { get; set; }
///
/// If true, only show what would be executed without applying.
///
public bool DryRun { get; set; }
///
/// Timeout in seconds for individual migration execution. Default: 300.
///
public int TimeoutSeconds { get; set; } = 300;
///
/// If true, validate checksums before applying new migrations.
///
public bool ValidateChecksums { get; set; } = true;
///
/// If true, fail if checksum validation errors are found.
///
public bool FailOnChecksumMismatch { get; set; } = true;
}
///
/// Result of a migration execution.
///
public sealed class MigrationResult
{
///
/// Whether the migration run was successful.
///
public bool Success { get; init; }
///
/// Number of migrations applied.
///
public int AppliedCount { get; init; }
///
/// Number of migrations skipped (already applied).
///
public int SkippedCount { get; init; }
///
/// Number of migrations filtered out by category.
///
public int FilteredCount { get; init; }
///
/// Total duration in milliseconds.
///
public long DurationMs { get; init; }
///
/// Details of applied migrations.
///
public IReadOnlyList AppliedMigrations { get; init; } = [];
///
/// Checksum validation errors, if any.
///
public IReadOnlyList ChecksumErrors { get; init; } = [];
///
/// Error message if migration failed.
///
public string? ErrorMessage { get; init; }
///
/// Creates a successful result.
///
public static MigrationResult Successful(
int appliedCount,
int skippedCount,
int filteredCount,
long durationMs,
IReadOnlyList appliedMigrations) => new()
{
Success = true,
AppliedCount = appliedCount,
SkippedCount = skippedCount,
FilteredCount = filteredCount,
DurationMs = durationMs,
AppliedMigrations = appliedMigrations
};
///
/// Creates a failed result.
///
public static MigrationResult Failed(string errorMessage, IReadOnlyList? checksumErrors = null) => new()
{
Success = false,
ErrorMessage = errorMessage,
ChecksumErrors = checksumErrors ?? []
};
}
///
/// Details of an applied migration.
///
public sealed record AppliedMigrationDetail(
string Name,
MigrationCategory Category,
long DurationMs,
bool WasDryRun);