using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using FluentMigrator.Expressions; using FluentMigrator.Model; using FluentMigrator.Runner.Generators.SQLite; using FluentMigrator.Runner.Initialization; using FluentMigrator.Runner.Processors; using FluentMigrator.Runner.Processors.SQLite; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace NzbDrone.Core.Datastore.Migration.Framework { public class NzbDroneSQLiteProcessor : SQLiteProcessor { public NzbDroneSQLiteProcessor(SQLiteDbFactory factory, SQLiteGenerator generator, ILogger logger, IOptionsSnapshot options, IConnectionStringAccessor connectionStringAccessor, IServiceProvider serviceProvider, SQLiteQuoter sqliteQuoter) : base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, sqliteQuoter) { } public override void Process(AlterColumnExpression expression) { var tableDefinition = GetTableSchema(expression.TableName); var columnDefinitions = tableDefinition.Columns.ToList(); var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.Column.Name); if (columnIndex == -1) { throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName)); } columnDefinitions[columnIndex] = expression.Column; tableDefinition.Columns = columnDefinitions; ProcessAlterTable(tableDefinition); } public override void Process(DeleteColumnExpression expression) { var tableDefinition = GetTableSchema(expression.TableName); var columnDefinitions = tableDefinition.Columns.ToList(); var indexDefinitions = tableDefinition.Indexes.ToList(); var columnsToRemove = expression.ColumnNames.ToList(); columnDefinitions.RemoveAll(c => columnsToRemove.Remove(c.Name)); indexDefinitions.RemoveAll(i => i.Columns.Any(c => expression.ColumnNames.Contains(c.Name))); tableDefinition.Columns = columnDefinitions; tableDefinition.Indexes = indexDefinitions; if (columnsToRemove.Any()) { throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName)); } ProcessAlterTable(tableDefinition); } public override void Process(RenameColumnExpression expression) { var tableDefinition = GetTableSchema(expression.TableName); var oldColumnDefinitions = tableDefinition.Columns.ToList(); var columnDefinitions = tableDefinition.Columns.ToList(); var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.OldName); if (columnIndex == -1) { throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, 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)); } oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone(); columnDefinitions[columnIndex].Name = expression.NewName; foreach (var index in tableDefinition.Indexes) { if (index.Name.StartsWith("IX_")) { index.Name = Regex.Replace(index.Name, "(?<=_)" + Regex.Escape(expression.OldName) + "(?=_|$)", Regex.Escape(expression.NewName)); } foreach (var column in index.Columns) { if (column.Name == expression.OldName) { column.Name = expression.NewName; } } } ProcessAlterTable(tableDefinition, oldColumnDefinitions); } protected virtual TableDefinition GetTableSchema(string tableName) { var schemaDumper = new SqliteSchemaDumper(this); var schema = schemaDumper.ReadDbSchema(); return schema.Single(v => v.Name == tableName); } protected virtual void ProcessAlterTable(TableDefinition tableDefinition, List oldColumnDefinitions = null) { var tableName = tableDefinition.Name; var tempTableName = tableName + "_temp"; var uid = 0; while (TableExists(null, tempTableName)) { tempTableName = tableName + "_temp" + uid++; } // What is the cleanest way to do this? Add function to Generator? var quoter = new SQLiteQuoter(); 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(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName))); Process(new DeleteTableExpression() { TableName = tableName }); Process(new RenameTableExpression() { OldName = tempTableName, NewName = tableName }); foreach (var index in tableDefinition.Indexes) { Process(new CreateIndexExpression() { Index = index }); } } } }