From d4426fe0f62826b243a3f40b0705c2ce8b4e645d Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Sun, 8 Mar 2026 11:13:39 -0400 Subject: [PATCH] Use psql subprocess to execute schema script - handles psql meta-commands --- .../new-dotnet-version_02bc64/scenario.json | 2 +- .../PostgresDatabaseProvider.cs | 71 ++++++++++++++++--- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/.github/upgrades/scenarios/new-dotnet-version_02bc64/scenario.json b/.github/upgrades/scenarios/new-dotnet-version_02bc64/scenario.json index 1d47742c..b705cb02 100644 --- a/.github/upgrades/scenarios/new-dotnet-version_02bc64/scenario.json +++ b/.github/upgrades/scenarios/new-dotnet-version_02bc64/scenario.json @@ -3,7 +3,7 @@ "operationId": "02bc6448-a69d-4ff4-a0ec-0db07ec3a09a", "description": "Upgrade solution or project to new version of .NET", "startTime": "2026-03-05T22:18:32.3921759Z", - "lastUpdateTime": "2026-03-08T15:04:52.4729284Z", + "lastUpdateTime": "2026-03-08T15:11:47.2281903Z", "stage": "Execution", "properties": {}, "folderPath": "" diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs b/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs index 27d492c6..9a55f72b 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs @@ -8,6 +8,7 @@ namespace Jellyfin.Database.Providers.Postgres; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -418,24 +419,74 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider try { - var schemaScript = await File.ReadAllTextAsync(schemaScriptPath, cancellationToken).ConfigureAwait(false); + // The SQL file contains psql meta-commands (\restrict, etc.) that can't be executed via ExecuteNonQuery + // We need to use psql subprocess instead + var connectionString = connection.ConnectionString; + var builder = new NpgsqlConnectionStringBuilder(connectionString); - // Split script by statement terminator and execute each part - // PostgreSQL dump scripts may have multiple statements - await using (var command = connection.CreateCommand()) + // Build psql command + var psqlArgs = $"-h {builder.Host} -p {builder.Port} -U {builder.Username} -d {builder.Database} -f \"{schemaScriptPath}\""; + + logger.LogInformation("Executing via psql: psql {Args}", psqlArgs.Replace(builder.Password ?? string.Empty, "***", StringComparison.Ordinal)); + + var psqlProcess = new System.Diagnostics.Process { - command.CommandText = schemaScript; - command.CommandTimeout = 600; // 10 minutes - await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "psql", + Arguments = psqlArgs, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + // Set password via environment variable + if (!string.IsNullOrEmpty(builder.Password)) + { + psqlProcess.StartInfo.EnvironmentVariables["PGPASSWORD"] = builder.Password; } - logger.LogInformation("✅ Successfully initialized database from SQL script"); - logger.LogInformation("Database is ready. You can now start Jellyfin."); + psqlProcess.Start(); + + // Read output + var output = await psqlProcess.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + var error = await psqlProcess.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + + await psqlProcess.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + + if (psqlProcess.ExitCode == 0) + { + logger.LogInformation("✅ Successfully initialized database from SQL script via psql"); + logger.LogInformation("Database is ready. You can now start Jellyfin."); + if (!string.IsNullOrWhiteSpace(output)) + { + logger.LogDebug("psql output: {Output}", output); + } + } + else + { + logger.LogError("psql exited with code {ExitCode}", psqlProcess.ExitCode); + if (!string.IsNullOrWhiteSpace(error)) + { + logger.LogError("psql error: {Error}", error); + } + throw new InvalidOperationException($"psql failed with exit code {psqlProcess.ExitCode}. Error: {error}"); + } + } + catch (System.ComponentModel.Win32Exception psqlNotFound) + { + // psql not found in PATH - fallback to manual instructions + logger.LogWarning(psqlNotFound, "psql command not found in PATH. Cannot automatically initialize database."); + logger.LogError("Please run the SQL script manually: psql -U postgres -d jellyfin_testdata -f {Path}", schemaScriptPath); + throw new FileNotFoundException("psql command not found. Please install PostgreSQL client tools and ensure psql is in your PATH, or run the SQL script manually.", psqlNotFound); } catch (Exception scriptEx) { logger.LogError(scriptEx, "Failed to execute schema creation script"); - logger.LogError("Please run the SQL script manually: psql -U jellyfin -d jellyfin -f {Path}", schemaScriptPath); + logger.LogError("Please run the SQL script manually: psql -U postgres -d jellyfin_testdata -f {Path}", schemaScriptPath); throw; } }