From 75acefe9896c938cd4609af7ae5b9a4dc25de2bb Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Sat, 7 Mar 2026 13:07:44 -0500 Subject: [PATCH] Add automatic SQL script execution for empty database initialization --- .../new-dotnet-version_02bc64/scenario.json | 2 +- .../PostgresDatabaseProvider.cs | 102 +++++++++++++++--- 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/.github/upgrades/scenarios/new-dotnet-version_02bc64/scenario.json b/.github/upgrades/scenarios/new-dotnet-version_02bc64/scenario.json index 5696c056..9dc202a5 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-07T17:59:55.6322536Z", + "lastUpdateTime": "2026-03-07T18:05:10.0632722Z", "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 66491de8..d2f925cf 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs @@ -380,11 +380,89 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider var context = await DbContextFactory!.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); await using (context.ConfigureAwait(false)) { - // DISABLED for .NET 11 preview: EnsureCreatedAsync bypasses migrations and creates schema directly - // This prevents proper migration tracking. Use manual SQL scripts instead. - // await context.Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false); + // Check if database is empty (no schema or no tables) and initialize if needed + var connection = context.Database.GetDbConnection(); + var wasConnectionOpened = false; - logger.LogInformation("Database schema creation disabled for .NET 11 preview. Database must be initialized using SQL scripts in sql/schema_init/ directory."); + try + { + if (connection.State != System.Data.ConnectionState.Open) + { + await connection.OpenAsync(cancellationToken).ConfigureAwait(false); + wasConnectionOpened = true; + } + + // Check if the library schema exists + var schemaCheckQuery = "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = 'library'"; + await using (var schemaCheckCommand = connection.CreateCommand()) + { + schemaCheckCommand.CommandText = schemaCheckQuery; + var schemaExists = (long?)await schemaCheckCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); + + if (schemaExists == 0) + { + logger.LogInformation("Database is empty (library schema not found). Attempting to initialize from SQL script..."); + + // Try to load and execute create_database_schema.sql + var schemaScriptPath = Path.Combine(AppContext.BaseDirectory, "sql", "schema_init", "create_database_schema.sql"); + + if (File.Exists(schemaScriptPath)) + { + logger.LogInformation("Found schema script at: {Path}", schemaScriptPath); + logger.LogInformation("Executing create_database_schema.sql to initialize database..."); + + try + { + var schemaScript = await File.ReadAllTextAsync(schemaScriptPath, cancellationToken).ConfigureAwait(false); + + // Execute the schema creation script + await using (var schemaCommand = connection.CreateCommand()) + { + schemaCommand.CommandText = schemaScript; + schemaCommand.CommandTimeout = 600; // 10 minutes for large schema creation + await schemaCommand.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); + } + + logger.LogInformation("Successfully initialized database from SQL script"); + } + catch (Exception scriptEx) + { + logger.LogError(scriptEx, "Failed to execute schema creation script. You may need to run it manually: {Path}", schemaScriptPath); + throw; + } + } + else + { + logger.LogError("Database is empty but schema script not found at: {Path}", schemaScriptPath); + logger.LogError("Please create the database manually using the SQL script from sql/schema_init/create_database_schema.sql"); + throw new FileNotFoundException($"Schema initialization script not found: {schemaScriptPath}"); + } + } + } + + // DISABLED for .NET 11 preview: EnsureCreatedAsync bypasses migrations and creates schema directly + // This prevents proper migration tracking. Use manual SQL scripts instead. + // await context.Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false); + + logger.LogInformation("Database schema verification complete."); + + // Close connection before getting migrations to allow EF to manage connections + if (wasConnectionOpened && connection.State == System.Data.ConnectionState.Open) + { + await connection.CloseAsync().ConfigureAwait(false); + wasConnectionOpened = false; + } + } + catch + { + // Ensure connection is closed on error + if (wasConnectionOpened && connection.State == System.Data.ConnectionState.Open) + { + await connection.CloseAsync().ConfigureAwait(false); + } + + throw; + } // Get applied migrations var appliedMigrations = await context.Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); @@ -446,17 +524,15 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider } // Verify all expected tables exist in each schema - var connection = context.Database.GetDbConnection(); - var wasOpened = false; + // Reopen connection for verification + if (connection.State != System.Data.ConnectionState.Open) + { + await connection.OpenAsync(cancellationToken).ConfigureAwait(false); + wasConnectionOpened = true; + } try { - if (connection.State != System.Data.ConnectionState.Open) - { - await connection.OpenAsync(cancellationToken).ConfigureAwait(false); - wasOpened = true; - } - foreach (var schema in Schemas.All) { // Count tables in the schema @@ -490,7 +566,7 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider } finally { - if (wasOpened) + if (wasConnectionOpened && connection.State == System.Data.ConnectionState.Open) { await connection.CloseAsync().ConfigureAwait(false); }