81 lines
4.2 KiB
C#
81 lines
4.2 KiB
C#
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);
|
|
}
|