COMPLETE REWRITE: Remove ALL migration code, implement pure SQL script initialization
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"operationId": "02bc6448-a69d-4ff4-a0ec-0db07ec3a09a",
|
"operationId": "02bc6448-a69d-4ff4-a0ec-0db07ec3a09a",
|
||||||
"description": "Upgrade solution or project to new version of .NET",
|
"description": "Upgrade solution or project to new version of .NET",
|
||||||
"startTime": "2026-03-05T22:18:32.3921759Z",
|
"startTime": "2026-03-05T22:18:32.3921759Z",
|
||||||
"lastUpdateTime": "2026-03-07T18:05:10.0632722Z",
|
"lastUpdateTime": "2026-03-07T18:26:16.052623Z",
|
||||||
"stage": "Execution",
|
"stage": "Execution",
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"folderPath": ""
|
"folderPath": ""
|
||||||
|
|||||||
@@ -117,12 +117,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Post-build event to ensure SQL files are copied -->
|
<!-- Post-build event to ensure SQL files are copied -->
|
||||||
<Target Name="CopySQLFiles" AfterTargets="Build">
|
<Target Name="CopySQLFiles" AfterTargets="AfterBuild">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<SQLFiles Include="$(ProjectDir)sql\**\*.sql" />
|
<SQLFiles Include="$(MSBuildProjectDirectory)\sql\**\*.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Copy SourceFiles="@(SQLFiles)" DestinationFolder="$(OutDir)sql\%(RecursiveDir)" SkipUnchangedFiles="true" />
|
<Message Importance="high" Text="Copying SQL files to $(OutDir)sql\" />
|
||||||
<Message Importance="high" Text="Copied SQL files to output directory" />
|
<Copy SourceFiles="@(SQLFiles)" DestinationFiles="@(SQLFiles->'$(OutDir)sql\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
|
||||||
|
<Message Importance="high" Text="✅ SQL files copied to output directory: $(OutDir)sql\" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+48
-137
@@ -375,12 +375,11 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
logger.LogWarning("Database configuration not available. Database existence check skipped.");
|
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);
|
var context = await DbContextFactory!.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await using (context.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 connection = context.Database.GetDbConnection();
|
||||||
var wasConnectionOpened = false;
|
var wasConnectionOpened = false;
|
||||||
|
|
||||||
@@ -392,160 +391,67 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
wasConnectionOpened = true;
|
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'";
|
var schemaCheckQuery = "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = 'library'";
|
||||||
await using (var schemaCheckCommand = connection.CreateCommand())
|
await using (var schemaCheckCommand = connection.CreateCommand())
|
||||||
{
|
{
|
||||||
schemaCheckCommand.CommandText = schemaCheckQuery;
|
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");
|
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.LogError("Schema script not found at: {Path}", schemaScriptPath);
|
||||||
logger.LogInformation("Executing create_database_schema.sql to initialize database...");
|
logger.LogError("Cannot initialize database automatically. Please run the SQL script manually.");
|
||||||
|
|
||||||
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}");
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
|
||||||
// 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
|
|
||||||
{
|
{
|
||||||
await RecordMigrationAsAppliedAsync(context, pendingMigrationsList, cancellationToken).ConfigureAwait(false);
|
logger.LogInformation("Database schema exists (library schema found)");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
// Verify all expected schemas and tables exist
|
||||||
// Reopen connection for verification
|
|
||||||
if (connection.State != System.Data.ConnectionState.Open)
|
|
||||||
{
|
|
||||||
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
wasConnectionOpened = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var schema in Schemas.All)
|
foreach (var schema in Schemas.All)
|
||||||
{
|
{
|
||||||
// Count tables in the schema
|
var tableCountQuery = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'";
|
||||||
var countQuery = @"
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = @schema AND table_type = 'BASE TABLE'";
|
|
||||||
|
|
||||||
await using (var command = connection.CreateCommand())
|
await using (var command = connection.CreateCommand())
|
||||||
{
|
{
|
||||||
command.CommandText = countQuery;
|
command.CommandText = tableCountQuery;
|
||||||
var parameter = command.CreateParameter();
|
var parameter = command.CreateParameter();
|
||||||
parameter.ParameterName = "@schema";
|
|
||||||
parameter.Value = schema;
|
parameter.Value = schema;
|
||||||
command.Parameters.Add(parameter);
|
command.Parameters.Add(parameter);
|
||||||
|
|
||||||
@@ -557,12 +463,12 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
}
|
}
|
||||||
else
|
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
|
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)
|
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;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user