using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Microsoft.EntityFrameworkCore;
namespace Jellyfin.Server.Implementations.Migrations;
///
/// Plain-SQL tracker for the EF migrations history table.
/// Replaces the EF-internal IHistoryRepository / SnakeCaseHistoryRepository.
///
public static class MigrationTracker
{
///
/// Entry name used when writing/reading migration history from backup archives.
/// Kept as "HistoryRow" for backward compatibility with existing backup files.
///
public const string BackupEntryName = "HistoryRow";
private const string CreateTableSql = """
CREATE TABLE IF NOT EXISTS public."__EFMigrationsHistory" (
migration_id character varying(150) NOT NULL,
product_version character varying(32) NOT NULL,
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY (migration_id)
)
""";
/// Ensures the migrations history table exists.
/// The database context.
/// Cancellation token.
/// A representing the operation.
public static async Task EnsureTableExistsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
=> await dbContext.Database.ExecuteSqlRawAsync(CreateTableSql, ct).ConfigureAwait(false);
/// Returns the migration_id of every applied migration.
/// The database context.
/// Cancellation token.
/// A read-only list of applied migration identifiers.
public static async Task> GetAppliedMigrationIdsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
=> await dbContext.Database
.SqlQueryRaw("""SELECT migration_id AS "Value" FROM public."__EFMigrationsHistory" """)
.ToListAsync(ct).ConfigureAwait(false);
/// Returns all applied migration rows.
/// The database context.
/// Cancellation token.
/// A read-only list of instances.
public static async Task> GetAppliedMigrationsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
=> await dbContext.Database
.SqlQueryRaw("""
SELECT migration_id AS "MigrationId", product_version AS "ProductVersion"
FROM public."__EFMigrationsHistory"
""")
.ToListAsync(ct).ConfigureAwait(false);
/// Inserts a migration row, ignoring conflicts.
/// The database context.
/// The migration identifier.
/// The product version.
/// Cancellation token.
/// A representing the operation.
public static async Task InsertAsync(JellyfinDbContext dbContext, string migrationId, string productVersion, CancellationToken ct = default)
=> await dbContext.Database.ExecuteSqlAsync(
$"""
INSERT INTO public."__EFMigrationsHistory" (migration_id, product_version)
VALUES ({migrationId}, {productVersion}) ON CONFLICT DO NOTHING
""",
ct).ConfigureAwait(false);
/// Deletes a migration row by id.
/// The database context.
/// The migration identifier to delete.
/// Cancellation token.
/// A representing the operation.
public static async Task DeleteAsync(JellyfinDbContext dbContext, string migrationId, CancellationToken ct = default)
=> await dbContext.Database.ExecuteSqlAsync(
$"""DELETE FROM public."__EFMigrationsHistory" WHERE migration_id = {migrationId}""",
ct).ConfigureAwait(false);
}