Implement PostgreSqlDatabaseProvider methods and DI registration (#6)

* Initial plan

* feat: implement PostgreSqlDatabaseProvider all methods + DI registration

Co-authored-by: ZoltyMat <177592743+ZoltyMat@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ZoltyMat <177592743+ZoltyMat@users.noreply.github.com>
This commit is contained in:
Copilot
2026-03-04 19:56:57 -05:00
committed by mat
parent 9a99572354
commit 2d4d6d8c38
3 changed files with 45 additions and 8 deletions
@@ -6,6 +6,7 @@ using System.Reflection;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.DbConfiguration;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.PostgreSQL;
using Jellyfin.Database.Providers.Sqlite;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
@@ -24,6 +25,7 @@ public static class ServiceCollectionExtensions
private static IEnumerable<Type> DatabaseProviderTypes()
{
yield return typeof(SqliteDatabaseProvider);
yield return typeof(PostgreSqlDatabaseProvider);
}
private static IDictionary<string, JellyfinDbProviderFactory> GetSupportedDbProviders()
@@ -36,6 +36,7 @@
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj" />
<ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Providers.PostgreSQL\Jellyfin.Database.Providers.PostgreSQL.csproj" />
<ProjectReference Include="..\src\Jellyfin.Database\Jellyfin.Database.Providers.Sqlite\Jellyfin.Database.Providers.Sqlite.csproj" />
</ItemGroup>
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
@@ -14,13 +15,28 @@ namespace Jellyfin.Database.Providers.PostgreSQL;
[JellyfinDatabaseProviderKey("Jellyfin-PostgreSQL")]
public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
{
private const string BackupNotSupportedMessage =
"Automated migration backups are not supported for PostgreSQL. Use the jellyfin-pg-backup CronJob for nightly S3 backups.";
/// <inheritdoc/>
public IDbContextFactory<JellyfinDbContext>? DbContextFactory { get; set; }
/// <inheritdoc/>
public void Initialise(DbContextOptionsBuilder options, DatabaseConfigurationOptions databaseConfiguration)
{
throw new NotImplementedException();
var connectionString =
Environment.GetEnvironmentVariable("POSTGRES_CONNECTION_STRING")
?? databaseConfiguration.CustomProviderOptions?.Options
?.FirstOrDefault(o => o.Key.Equals("ConnectionString", StringComparison.OrdinalIgnoreCase))
?.Value
?? databaseConfiguration.CustomProviderOptions?.ConnectionString
?? throw new InvalidOperationException(
"No PostgreSQL connection string found. Set the POSTGRES_CONNECTION_STRING environment variable, " +
"or provide it via CustomProviderOptions.Options[\"ConnectionString\"] or CustomProviderOptions.ConnectionString.");
options.UseNpgsql(
connectionString,
o => o.MigrationsAssembly(GetType().Assembly.FullName));
}
/// <inheritdoc/>
@@ -34,9 +50,13 @@ public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
}
/// <inheritdoc/>
public Task RunScheduledOptimisation(CancellationToken cancellationToken)
public async Task RunScheduledOptimisation(CancellationToken cancellationToken)
{
return Task.CompletedTask;
var context = await DbContextFactory!.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
await using (context.ConfigureAwait(false))
{
await context.Database.ExecuteSqlRawAsync("ANALYZE", cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc/>
@@ -48,24 +68,38 @@ public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
/// <inheritdoc/>
public Task<string> MigrationBackupFast(CancellationToken cancellationToken)
{
throw new NotImplementedException();
throw new NotSupportedException(BackupNotSupportedMessage);
}
/// <inheritdoc/>
public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
{
throw new NotImplementedException();
throw new NotSupportedException(BackupNotSupportedMessage);
}
/// <inheritdoc/>
public Task DeleteBackup(string key)
{
throw new NotImplementedException();
throw new NotSupportedException(BackupNotSupportedMessage);
}
/// <inheritdoc/>
public Task PurgeDatabase(JellyfinDbContext dbContext, IEnumerable<string>? tableNames)
public async Task PurgeDatabase(JellyfinDbContext dbContext, IEnumerable<string>? tableNames)
{
throw new NotImplementedException();
ArgumentNullException.ThrowIfNull(tableNames);
await dbContext.Database.ExecuteSqlRawAsync("SET session_replication_role = 'replica'").ConfigureAwait(false);
try
{
foreach (var tableName in tableNames)
{
var truncateSql = "TRUNCATE TABLE \"" + tableName + "\" CASCADE";
await dbContext.Database.ExecuteSqlRawAsync(truncateSql).ConfigureAwait(false);
}
}
finally
{
await dbContext.Database.ExecuteSqlRawAsync("SET session_replication_role = 'origin'").ConfigureAwait(false);
}
}
}