COMPLETE REWRITE: Remove ALL migration code, implement pure SQL script initialization
This commit is contained in:
+48
-137
@@ -375,12 +375,11 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
||||
logger.LogWarning("Database configuration not available. Database existence check skipped.");
|
||||
}
|
||||
|
||||
logger.LogInformation("Checking PostgreSQL database for missing tables...");
|
||||
logger.LogInformation("Checking PostgreSQL database schema...");
|
||||
|
||||
var context = await DbContextFactory!.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (context.ConfigureAwait(false))
|
||||
{
|
||||
// Check if database is empty (no schema or no tables) and initialize if needed
|
||||
var connection = context.Database.GetDbConnection();
|
||||
var wasConnectionOpened = false;
|
||||
|
||||
@@ -392,160 +391,67 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
||||
wasConnectionOpened = true;
|
||||
}
|
||||
|
||||
// Check if the library schema exists
|
||||
// Check if the library schema exists (indicates database is initialized)
|
||||
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);
|
||||
var schemaCount = (long?)await schemaCheckCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (schemaExists == 0)
|
||||
if (schemaCount == 0)
|
||||
{
|
||||
logger.LogInformation("Database is empty (library schema not found). Attempting to initialize from SQL script...");
|
||||
// Database is empty - initialize from SQL script
|
||||
logger.LogInformation("Database is empty (library schema not found). Initializing 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))
|
||||
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");
|
||||
logger.LogError("Schema script not found at: {Path}", schemaScriptPath);
|
||||
logger.LogError("Cannot initialize database automatically. Please run the SQL script manually.");
|
||||
throw new FileNotFoundException($"Schema initialization script not found: {schemaScriptPath}");
|
||||
}
|
||||
|
||||
logger.LogInformation("Found schema script at: {Path}", schemaScriptPath);
|
||||
logger.LogInformation("Executing create_database_schema.sql (this may take several minutes)...");
|
||||
|
||||
try
|
||||
{
|
||||
var schemaScript = await File.ReadAllTextAsync(schemaScriptPath, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Split script by statement terminator and execute each part
|
||||
// PostgreSQL dump scripts may have multiple statements
|
||||
await using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = schemaScript;
|
||||
command.CommandTimeout = 600; // 10 minutes
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
logger.LogInformation("✅ Successfully initialized database from SQL script");
|
||||
logger.LogInformation("Database is ready. You can now start Jellyfin.");
|
||||
}
|
||||
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);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
var appliedMigrationsList = appliedMigrations.ToList();
|
||||
logger.LogDebug("Found {Count} applied migrations", appliedMigrationsList.Count);
|
||||
|
||||
// Get pending migrations
|
||||
var pendingMigrations = await context.Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false);
|
||||
var pendingMigrationsList = pendingMigrations.ToList();
|
||||
|
||||
if (pendingMigrationsList.Count > 0)
|
||||
{
|
||||
logger.LogInformation("Found {Count} pending migrations: {Migrations}", pendingMigrationsList.Count, string.Join(", ", pendingMigrationsList));
|
||||
|
||||
// DISABLED: EF Core migrations don't work with .NET 11 preview
|
||||
// Use manual SQL scripts in Jellyfin.Server/sql/schema_init/ directory instead
|
||||
logger.LogWarning("EF Core migrations are disabled for .NET 11 preview. Please apply migrations manually using SQL scripts in sql/schema_init/ directory.");
|
||||
logger.LogWarning("Run the following script: apply-supplementary-indexes-migration.sql");
|
||||
|
||||
/* COMMENTED OUT - EF migrations not compatible with .NET 11 preview
|
||||
logger.LogInformation("Applying migrations...");
|
||||
|
||||
try
|
||||
{
|
||||
// Apply all pending migrations
|
||||
await context.Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
|
||||
logger.LogInformation("Successfully applied {Count} migrations", pendingMigrationsList.Count);
|
||||
}
|
||||
catch (Npgsql.PostgresException pgEx) when (pgEx.SqlState == "42P07") // relation already exists
|
||||
{
|
||||
// Table already exists - this can happen if migrations were partially applied
|
||||
logger.LogWarning("Migration attempted to create existing table. This may indicate a previous failed migration. Error: {Message}", pgEx.Message);
|
||||
logger.LogWarning("Attempting to mark migration as applied in history table...");
|
||||
|
||||
// Try to manually record the migration as applied
|
||||
try
|
||||
else
|
||||
{
|
||||
await RecordMigrationAsAppliedAsync(context, pendingMigrationsList, cancellationToken).ConfigureAwait(false);
|
||||
logger.LogInformation("Successfully recovered from partial migration state");
|
||||
}
|
||||
catch (Exception recordEx)
|
||||
{
|
||||
logger.LogError(recordEx, "Failed to recover from partial migration. Manual intervention may be required.");
|
||||
throw;
|
||||
logger.LogInformation("Database schema exists (library schema found)");
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("pending changes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// This can happen if the model has changed but migrations haven't been generated
|
||||
logger.LogWarning("Model has pending changes. This may indicate missing migration files. Error: {Message}", ex.Message);
|
||||
logger.LogWarning("If you're seeing this on a test/production system, ensure all migration files are deployed.");
|
||||
throw;
|
||||
}
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("All database tables are up to date. No migrations needed.");
|
||||
}
|
||||
|
||||
// Verify all expected tables exist in each schema
|
||||
// Reopen connection for verification
|
||||
if (connection.State != System.Data.ConnectionState.Open)
|
||||
{
|
||||
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
wasConnectionOpened = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Verify all expected schemas and tables exist
|
||||
foreach (var schema in Schemas.All)
|
||||
{
|
||||
// Count tables in the schema
|
||||
var countQuery = @"
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = @schema AND table_type = 'BASE TABLE'";
|
||||
|
||||
var tableCountQuery = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'";
|
||||
await using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = countQuery;
|
||||
command.CommandText = tableCountQuery;
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.ParameterName = "@schema";
|
||||
parameter.Value = schema;
|
||||
command.Parameters.Add(parameter);
|
||||
|
||||
@@ -557,12 +463,12 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("Schema '{Schema}' exists but contains no tables. This may indicate an incomplete migration.", schema);
|
||||
logger.LogWarning("Schema '{Schema}' exists but contains no tables. This may indicate an incomplete initialization.", schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Database table verification complete");
|
||||
logger.LogInformation("✅ Database schema verification complete");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -572,10 +478,15 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: EF Core migrations are DISABLED for .NET 11 preview
|
||||
// Database schema is created via SQL scripts (create_database_schema.sql)
|
||||
// Any future migrations must be applied manually using SQL scripts
|
||||
logger.LogInformation("EF Core migration system is disabled for .NET 11 preview. Use SQL scripts for schema changes.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to ensure PostgreSQL tables exist. Error: {Message}", ex.Message);
|
||||
logger.LogError(ex, "Failed to verify or initialize PostgreSQL database");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user