feat: implement migration tracking and history management for PostgreSQL
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Plain-SQL tracker for the EF migrations history table.
|
||||
/// Replaces the EF-internal IHistoryRepository / SnakeCaseHistoryRepository.
|
||||
/// </summary>
|
||||
public static class MigrationTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry name used when writing/reading migration history from backup archives.
|
||||
/// Kept as "HistoryRow" for backward compatibility with existing backup files.
|
||||
/// </summary>
|
||||
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)
|
||||
)
|
||||
""";
|
||||
|
||||
/// <summary>Ensures the migrations history table exists.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||
public static async Task EnsureTableExistsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
|
||||
=> await dbContext.Database.ExecuteSqlRawAsync(CreateTableSql, ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Returns the migration_id of every applied migration.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A read-only list of applied migration identifiers.</returns>
|
||||
public static async Task<IReadOnlyList<string>> GetAppliedMigrationIdsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
|
||||
=> await dbContext.Database
|
||||
.SqlQueryRaw<string>("""SELECT migration_id AS "Value" FROM public."__EFMigrationsHistory" """)
|
||||
.ToListAsync(ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Returns all applied migration rows.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A read-only list of <see cref="MigrationRecord"/> instances.</returns>
|
||||
public static async Task<IReadOnlyList<MigrationRecord>> GetAppliedMigrationsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
|
||||
=> await dbContext.Database
|
||||
.SqlQueryRaw<MigrationRecord>("""
|
||||
SELECT migration_id AS "MigrationId", product_version AS "ProductVersion"
|
||||
FROM public."__EFMigrationsHistory"
|
||||
""")
|
||||
.ToListAsync(ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Inserts a migration row, ignoring conflicts.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="migrationId">The migration identifier.</param>
|
||||
/// <param name="productVersion">The product version.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||
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);
|
||||
|
||||
/// <summary>Deletes a migration row by id.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="migrationId">The migration identifier to delete.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user