diff --git a/src/NuGet.config b/src/NuGet.config index d09664bf0..47d68eb1d 100644 --- a/src/NuGet.config +++ b/src/NuGet.config @@ -7,6 +7,5 @@ - diff --git a/src/NzbDrone.Core/Datastore/Migration/000_database_engine_version_check.cs b/src/NzbDrone.Core/Datastore/Migration/000_database_engine_version_check.cs index 93bfc0afc..343bf3cde 100644 --- a/src/NzbDrone.Core/Datastore/Migration/000_database_engine_version_check.cs +++ b/src/NzbDrone.Core/Datastore/Migration/000_database_engine_version_check.cs @@ -7,7 +7,7 @@ namespace NzbDrone.Core.Datastore.Migration { [Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)] - public class DatabaseEngineVersionCheck : FluentMigrator.Migration + public class DatabaseEngineVersionCheck : ForwardOnlyMigration { protected readonly Logger _logger; @@ -22,11 +22,6 @@ public override void Up() IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion); } - public override void Down() - { - // No-op - } - private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran) { using (var versionCmd = conn.CreateCommand()) diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index 992f2a26f..dfdbf5217 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -6,7 +6,6 @@ using FluentMigrator.Runner.Initialization; using FluentMigrator.Runner.Processors; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using NLog; using NLog.Extensions.Logging; @@ -20,13 +19,10 @@ public interface IMigrationController public class MigrationController : IMigrationController { private readonly Logger _logger; - private readonly ILoggerProvider _migrationLoggerProvider; - public MigrationController(Logger logger, - ILoggerProvider migrationLoggerProvider) + public MigrationController(Logger logger) { _logger = logger; - _migrationLoggerProvider = migrationLoggerProvider; } public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType) @@ -35,16 +31,13 @@ public void Migrate(string connectionString, MigrationContext migrationContext, _logger.Info("*** Migrating {0} ***", connectionString); - ServiceProvider serviceProvider; - var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres"; - serviceProvider = new ServiceCollection() + var serviceProvider = new ServiceCollection() .AddLogging(b => b.AddNLog()) .AddFluentMigratorCore() .Configure(cfg => cfg.IncludeUntaggedMaintenances = true) - .ConfigureRunner( - builder => builder + .ConfigureRunner(builder => builder .AddPostgres() .AddNzbDroneSQLite() .WithGlobalConnectionString(connectionString) diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs index 3607ca1d7..ab390f6f7 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs @@ -4,9 +4,14 @@ using FluentMigrator.Builders.Create.Table; using FluentMigrator.Runner; using FluentMigrator.Runner.BatchParser; +using FluentMigrator.Runner.Generators; using FluentMigrator.Runner.Generators.SQLite; +using FluentMigrator.Runner.Initialization; +using FluentMigrator.Runner.Processors; using FluentMigrator.Runner.Processors.SQLite; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace NzbDrone.Core.Datastore.Migration.Framework { @@ -26,23 +31,40 @@ public static IDbCommand CreateCommand(this IDbConnection conn, IDbTransaction t return command; } - public static void AddParameter(this System.Data.IDbCommand command, object value) + public static void AddParameter(this IDbCommand command, object value) { var parameter = command.CreateParameter(); parameter.Value = value; command.Parameters.Add(parameter); } - public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder) + public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder, bool binaryGuid = false, bool useStrictTables = false) { builder.Services .AddTransient() .AddScoped() - .AddScoped() + .AddScoped(sp => + { + var factory = sp.GetService(); + var logger = sp.GetService>(); + var options = sp.GetService>(); + var connectionStringAccessor = sp.GetService(); + var sqliteQuoter = new SQLiteQuoter(false); + return new NzbDroneSQLiteProcessor(factory, sp.GetService(), logger, options, connectionStringAccessor, sp, sqliteQuoter); + }) + .AddScoped(_ => new NzbDroneSQLiteTypeMap(useStrictTables)) .AddScoped(sp => sp.GetRequiredService()) - .AddScoped() - .AddScoped() + .AddScoped( + sp => + { + var typeMap = sp.GetRequiredService(); + return new SQLiteGenerator( + new SQLiteQuoter(binaryGuid), + typeMap, + new OptionsWrapper(new GeneratorOptions())); + }) .AddScoped(sp => sp.GetRequiredService()); + return builder; } } diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteProcessor.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteProcessor.cs index e3f776e58..9acb0ec7c 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteProcessor.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteProcessor.cs @@ -15,15 +15,18 @@ namespace NzbDrone.Core.Datastore.Migration.Framework { public class NzbDroneSQLiteProcessor : SQLiteProcessor { + private readonly SQLiteQuoter _quoter; + public NzbDroneSQLiteProcessor(SQLiteDbFactory factory, SQLiteGenerator generator, ILogger logger, IOptionsSnapshot options, IConnectionStringAccessor connectionStringAccessor, IServiceProvider serviceProvider, - SQLiteQuoter sqliteQuoter) - : base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, sqliteQuoter) + SQLiteQuoter quoter) + : base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter) { + _quoter = quoter; } public override void Process(AlterColumnExpression expression) @@ -35,7 +38,7 @@ public override void Process(AlterColumnExpression expression) if (columnIndex == -1) { - throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName)); + throw new ApplicationException($"Column {expression.Column.Name} does not exist on table {expression.TableName}."); } columnDefinitions[columnIndex] = expression.Column; @@ -45,6 +48,28 @@ public override void Process(AlterColumnExpression expression) ProcessAlterTable(tableDefinition); } + public override void Process(AlterDefaultConstraintExpression expression) + { + var tableDefinition = GetTableSchema(expression.TableName); + + var columnDefinitions = tableDefinition.Columns.ToList(); + var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.ColumnName); + + if (columnIndex == -1) + { + throw new ApplicationException($"Column {expression.ColumnName} does not exist on table {expression.TableName}."); + } + + var changedColumn = columnDefinitions[columnIndex]; + changedColumn.DefaultValue = expression.DefaultValue; + + columnDefinitions[columnIndex] = changedColumn; + + tableDefinition.Columns = columnDefinitions; + + ProcessAlterTable(tableDefinition); + } + public override void Process(DeleteColumnExpression expression) { var tableDefinition = GetTableSchema(expression.TableName); @@ -62,7 +87,7 @@ public override void Process(DeleteColumnExpression expression) if (columnsToRemove.Any()) { - throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName)); + throw new ApplicationException($"Column {columnsToRemove.First()} does not exist on table {expression.TableName}."); } ProcessAlterTable(tableDefinition); @@ -78,12 +103,12 @@ public override void Process(RenameColumnExpression expression) if (columnIndex == -1) { - throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, expression.TableName)); + throw new ApplicationException($"Column {expression.OldName} does not exist on table {expression.TableName}."); } if (columnDefinitions.Any(c => c.Name == expression.NewName)) { - throw new ApplicationException(string.Format("Column {0} already exists on table {1}.", expression.NewName, expression.TableName)); + throw new ApplicationException($"Column {expression.NewName} already exists on table {expression.TableName}."); } oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone(); @@ -128,21 +153,20 @@ protected virtual void ProcessAlterTable(TableDefinition tableDefinition, List quoter.QuoteColumnName(c.Name))); - var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => quoter.QuoteColumnName(c.Name))); + var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => _quoter.QuoteColumnName(c.Name))); + var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => _quoter.QuoteColumnName(c.Name))); - Process(new CreateTableExpression() { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() }); + Process(new CreateTableExpression { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() }); - Process(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName))); + Process($"INSERT INTO {_quoter.QuoteTableName(tempTableName)} ({columnsToInsert}) SELECT {columnsToFetch} FROM {_quoter.QuoteTableName(tableName)}"); - Process(new DeleteTableExpression() { TableName = tableName }); + Process(new DeleteTableExpression { TableName = tableName }); - Process(new RenameTableExpression() { OldName = tempTableName, NewName = tableName }); + Process(new RenameTableExpression { OldName = tempTableName, NewName = tableName }); foreach (var index in tableDefinition.Indexes) { - Process(new CreateIndexExpression() { Index = index }); + Process(new CreateIndexExpression { Index = index }); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteTypeMap.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteTypeMap.cs new file mode 100644 index 000000000..2e431e2df --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneSQLiteTypeMap.cs @@ -0,0 +1,76 @@ +using System.Data; +using FluentMigrator.Runner.Generators.Base; +using FluentMigrator.Runner.Generators.SQLite; + +namespace NzbDrone.Core.Datastore.Migration.Framework; + +// Based on https://github.com/fluentmigrator/fluentmigrator/blob/v6.2.0/src/FluentMigrator.Runner.SQLite/Generators/SQLite/SQLiteTypeMap.cs +public sealed class NzbDroneSQLiteTypeMap : TypeMapBase, ISQLiteTypeMap +{ + public bool UseStrictTables { get; } + + public NzbDroneSQLiteTypeMap(bool useStrictTables = false) + { + UseStrictTables = useStrictTables; + + SetupTypeMaps(); + } + + // Must be kept in sync with upstream + protected override void SetupTypeMaps() + { + SetTypeMap(DbType.Binary, "BLOB"); + SetTypeMap(DbType.Byte, "INTEGER"); + SetTypeMap(DbType.Int16, "INTEGER"); + SetTypeMap(DbType.Int32, "INTEGER"); + SetTypeMap(DbType.Int64, "INTEGER"); + SetTypeMap(DbType.SByte, "INTEGER"); + SetTypeMap(DbType.UInt16, "INTEGER"); + SetTypeMap(DbType.UInt32, "INTEGER"); + SetTypeMap(DbType.UInt64, "INTEGER"); + + if (!UseStrictTables) + { + SetTypeMap(DbType.Currency, "NUMERIC"); + SetTypeMap(DbType.Decimal, "NUMERIC"); + SetTypeMap(DbType.Double, "NUMERIC"); + SetTypeMap(DbType.Single, "NUMERIC"); + SetTypeMap(DbType.VarNumeric, "NUMERIC"); + SetTypeMap(DbType.Date, "DATETIME"); + SetTypeMap(DbType.DateTime, "DATETIME"); + SetTypeMap(DbType.DateTime2, "DATETIME"); + SetTypeMap(DbType.Time, "DATETIME"); + SetTypeMap(DbType.Guid, "UNIQUEIDENTIFIER"); + + // Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing + SetTypeMap(DbType.DateTimeOffset, "DATETIME"); + } + else + { + SetTypeMap(DbType.Currency, "TEXT"); + SetTypeMap(DbType.Decimal, "TEXT"); + SetTypeMap(DbType.Double, "REAL"); + SetTypeMap(DbType.Single, "REAL"); + SetTypeMap(DbType.VarNumeric, "TEXT"); + SetTypeMap(DbType.Date, "TEXT"); + SetTypeMap(DbType.DateTime, "TEXT"); + SetTypeMap(DbType.DateTime2, "TEXT"); + SetTypeMap(DbType.Time, "TEXT"); + SetTypeMap(DbType.Guid, "TEXT"); + + // Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing + SetTypeMap(DbType.DateTimeOffset, "TEXT"); + } + + SetTypeMap(DbType.AnsiString, "TEXT"); + SetTypeMap(DbType.String, "TEXT"); + SetTypeMap(DbType.AnsiStringFixedLength, "TEXT"); + SetTypeMap(DbType.StringFixedLength, "TEXT"); + SetTypeMap(DbType.Boolean, "INTEGER"); + } + + public override string GetTypeMap(DbType type, int? size, int? precision) + { + return base.GetTypeMap(type, size: null, precision: null); + } +} diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 0723b4d34..470a415df 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -15,9 +15,9 @@ - - - + + +