From f314a7a411b9d19b9f47b96d42055f3e09c7b3af Mon Sep 17 00:00:00 2001 From: Alessandro Magistroni <65421435+AMagistroni@users.noreply.github.com> Date: Mon, 13 Sep 2021 21:58:24 +0200 Subject: [PATCH] drop index and create before alter column --- .../Common/RelatedDbObjects.cs | 5 +- .../DbStructures/DbObjectType.cs | 4 +- SqlSchemaCompare.Core/DbStructures/Index.cs | 6 +- SqlSchemaCompare.Core/DbStructures/Table.cs | 38 +++--- .../TSql/Factory/TSqlIndexFactory.cs | 4 +- .../TSql/Factory/TSqlTableFactory.cs | 61 +++++----- .../TSql/TSqlParserUpdateListener.cs | 4 +- .../TSql/TSqlSchemaBuilder.cs | 14 ++- SqlSchemaCompare.Core/UpdateSchemaManager.cs | 108 ++++++++++-------- SqlSchemaCompare.Test/TSql/TSqlIndexTest.cs | 68 ++++++++--- SqlSchemaCompare.Test/TSql/TSqlTableTest.cs | 60 +++++++++- .../MainForm.Designer.cs | 17 +-- SqlSchemaCompare.WindowsForm/MainForm.cs | 3 - .../RelatedDbObjects.cs | 28 ----- .../SqlSchemaCompare.WindowsForm.csproj | 2 +- 15 files changed, 243 insertions(+), 179 deletions(-) delete mode 100644 SqlSchemaCompare.WindowsForm/RelatedDbObjects.cs diff --git a/SqlSchemaCompare.Core/Common/RelatedDbObjects.cs b/SqlSchemaCompare.Core/Common/RelatedDbObjects.cs index 4423c20..5570589 100644 --- a/SqlSchemaCompare.Core/Common/RelatedDbObjects.cs +++ b/SqlSchemaCompare.Core/Common/RelatedDbObjects.cs @@ -6,18 +6,17 @@ namespace SqlSchemaCompare.Core.Common { public class RelatedDbObjectsConfiguration { - public static List> RelatedDbObjects = new List> + public static List> RelatedDbObjects = new() { new List { DbObjectType.Function }, new List { DbObjectType.StoreProcedure }, - new List { DbObjectType.Table, DbObjectType.TableContraint, DbObjectType.Column }, + new List { DbObjectType.Table, DbObjectType.TableDefaultContraint, DbObjectType.TableForeignKeyContraint, DbObjectType.TablePrimaryKeyContraint, DbObjectType.Column, DbObjectType.Index }, new List { DbObjectType.User, DbObjectType.Role, DbObjectType.Member }, new List { DbObjectType.View }, new List { DbObjectType.Schema }, new List { DbObjectType.Trigger, DbObjectType.EnableTrigger }, new List { DbObjectType.Type }, new List { DbObjectType.Other}, - new List { DbObjectType.Index } }; public List GetRelatedDbObjects(DbObjectType dbObject) diff --git a/SqlSchemaCompare.Core/DbStructures/DbObjectType.cs b/SqlSchemaCompare.Core/DbStructures/DbObjectType.cs index 6300a55..2f525b1 100644 --- a/SqlSchemaCompare.Core/DbStructures/DbObjectType.cs +++ b/SqlSchemaCompare.Core/DbStructures/DbObjectType.cs @@ -13,7 +13,9 @@ public enum DbObjectType Member, Type, Index, - TableContraint, + TableDefaultContraint, + TableForeignKeyContraint, + TablePrimaryKeyContraint, Column, EnableTrigger, Database, diff --git a/SqlSchemaCompare.Core/DbStructures/Index.cs b/SqlSchemaCompare.Core/DbStructures/Index.cs index 664194f..067243c 100644 --- a/SqlSchemaCompare.Core/DbStructures/Index.cs +++ b/SqlSchemaCompare.Core/DbStructures/Index.cs @@ -1,8 +1,10 @@ -namespace SqlSchemaCompare.Core.DbStructures +using System.Collections.Generic; + +namespace SqlSchemaCompare.Core.DbStructures { public class Index : DbObject { public override DbObjectType DbObjectType => DbObjectType.Index; - public string TableName { get; set; } + public IEnumerable ColumnNames { get; init; } } } diff --git a/SqlSchemaCompare.Core/DbStructures/Table.cs b/SqlSchemaCompare.Core/DbStructures/Table.cs index eb7809a..feb5722 100644 --- a/SqlSchemaCompare.Core/DbStructures/Table.cs +++ b/SqlSchemaCompare.Core/DbStructures/Table.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using SqlSchemaCompare.Core.DbStructures; +using System.Collections.Generic; namespace SqlSchemaCompare.Core.DbStructures { @@ -10,23 +11,34 @@ public class Column : DbObject { public IList Constraints { get; } = new List(); public Table Table { get; init; } } - public class TableConstraint : DbObject - { - public enum ConstraintTypes - { - Default, - ForeignKey, - PrimaryKey - } - public override DbObjectType DbObjectType => DbObjectType.TableContraint; - public IEnumerable ColumnName { get; init; } - public ConstraintTypes ConstraintType { get; init; } + + public class TableDefaultConstraint : TableConstraint + { + public override DbObjectType DbObjectType => DbObjectType.TableDefaultContraint; public string Value { get; init; } - public Table Table { get; init; } } + + public class TableForeignKeyConstraint : TableConstraint + { + public override DbObjectType DbObjectType => DbObjectType.TableForeignKeyContraint; + } + + public class TablePrimaryKeyConstraint : TableConstraint + { + public override DbObjectType DbObjectType => DbObjectType.TablePrimaryKeyContraint; + } + public IList Columns { get; } = new List(); public IList Constraints { get; } = new List(); + public IList Indexes { get; } = new List(); public void AdColumns(Column Colum) => Columns.Add(Colum); public void AddConstraint(TableConstraint tableConstraint) => Constraints.Add(tableConstraint); + public void AddIndex(Index index) => Indexes.Add(index); } } + +public abstract class TableConstraint : DbObject +{ + public Table Table { get; init; } + public IEnumerable ColumnNames { get; init; } +} \ No newline at end of file diff --git a/SqlSchemaCompare.Core/TSql/Factory/TSqlIndexFactory.cs b/SqlSchemaCompare.Core/TSql/Factory/TSqlIndexFactory.cs index 34b2a86..cb7917f 100644 --- a/SqlSchemaCompare.Core/TSql/Factory/TSqlIndexFactory.cs +++ b/SqlSchemaCompare.Core/TSql/Factory/TSqlIndexFactory.cs @@ -2,6 +2,7 @@ using Antlr4.Runtime.Misc; using SqlSchemaCompare.Core.Common; using SqlSchemaCompare.Core.DbStructures; +using System.Linq; namespace SqlSchemaCompare.Core.TSql.Factory { @@ -16,7 +17,8 @@ public DbObject Create(ParserRuleContext context, ICharStream stream) Name = indexContext.id_()[0].GetText(), Schema = string.Empty, Operation = GetOperation(indexContext.GetChild(0).GetText()), - TableName = indexContext.table_name().GetText() + ParentName = indexContext.table_name().GetText(), + ColumnNames = indexContext.column_name_list_with_order().id_().Select(x => x.GetText()), }; } } diff --git a/SqlSchemaCompare.Core/TSql/Factory/TSqlTableFactory.cs b/SqlSchemaCompare.Core/TSql/Factory/TSqlTableFactory.cs index 8fd4e97..ef505ef 100644 --- a/SqlSchemaCompare.Core/TSql/Factory/TSqlTableFactory.cs +++ b/SqlSchemaCompare.Core/TSql/Factory/TSqlTableFactory.cs @@ -2,6 +2,7 @@ using Antlr4.Runtime.Misc; using SqlSchemaCompare.Core.Common; using SqlSchemaCompare.Core.DbStructures; +using System; using System.Collections.Generic; using System.Linq; @@ -49,56 +50,54 @@ private Table.Column CreateColumn(TSqlParser.Column_def_table_constraintContext }; } - public Table.TableConstraint CreatePrimaryKeyConstraint(TSqlParser.Table_constraintContext constraintContext, ICharStream stream, Table table) + private Table.TablePrimaryKeyConstraint CreatePrimaryKeyConstraint(TSqlParser.Table_constraintContext constraintContext, ICharStream stream, Table table) { - return new Table.TableConstraint + return new Table.TablePrimaryKeyConstraint { Sql = stream.GetText(new Interval(constraintContext.start.StartIndex, constraintContext.stop.StopIndex)), Name = constraintContext.constraint?.GetText(), ParentName = table.Identifier, - ColumnName = constraintContext.column_name_list_with_order().id_().Select(x => x.GetText()), - ConstraintType = Table.TableConstraint.ConstraintTypes.PrimaryKey, + ColumnNames = constraintContext.column_name_list_with_order().id_().Select(x => x.GetText()), Table = table }; } - public Table.TableConstraint CreateAlterTable(ParserRuleContext context) + internal TableConstraint CreateAlterTable(ParserRuleContext context) { var alterTableContext = context as TSqlParser.Alter_tableContext; - var tableName = alterTableContext.children[2].GetText(); - string name = string.Empty; - string columnName = string.Empty; - string value = string.Empty; - - Table.TableConstraint.ConstraintTypes constraintType = Table.TableConstraint.ConstraintTypes.ForeignKey; if ((alterTableContext.fk != null) || (alterTableContext.constraint != null)) { - columnName = alterTableContext.fk?.GetText(); - name = alterTableContext.constraint != null ? alterTableContext.constraint.GetText() : default; - constraintType = Table.TableConstraint.ConstraintTypes.ForeignKey; + return CreateForeignKeyConstraint(alterTableContext); } else if (alterTableContext.column_def_table_constraints() != null) { - var constraint = ((TSqlParser.Column_def_table_constraintContext)alterTableContext.column_def_table_constraints().children[0]).table_constraint(); - if (constraint.CONSTRAINT() != null) - { - name = constraint.id_()[0].GetText(); - } - columnName = constraint.forColumn.GetText(); - constraintType = Table.TableConstraint.ConstraintTypes.Default; - if (constraint.DEFAULT() != null) - { - value = constraint.default_value_column.GetText(); - } + return CreateDefaultConstraint(alterTableContext); } - return new Table.TableConstraint + + throw new NotImplementedException(); + } + + private Table.TableForeignKeyConstraint CreateForeignKeyConstraint(TSqlParser.Alter_tableContext alterTableContext) + { + return new Table.TableForeignKeyConstraint + { + Sql = alterTableContext.Start.InputStream.GetText(new Interval(alterTableContext.start.StartIndex, alterTableContext.stop.StopIndex)), + Name = alterTableContext.constraint != null ? alterTableContext.constraint.GetText() : default, + ParentName = alterTableContext.children[2].GetText(), + ColumnNames = new List { alterTableContext.fk?.GetText() } + }; + } + + private Table.TableDefaultConstraint CreateDefaultConstraint(TSqlParser.Alter_tableContext alterTableContext) + { + var constraint = ((TSqlParser.Column_def_table_constraintContext)alterTableContext.column_def_table_constraints().children[0]).table_constraint(); + return new Table.TableDefaultConstraint { Sql = alterTableContext.Start.InputStream.GetText(new Interval(alterTableContext.start.StartIndex, alterTableContext.stop.StopIndex)), - Name = name, - ParentName = tableName, - ColumnName = new List {columnName}, - ConstraintType = constraintType, - Value = value.ToString() + Name = constraint.CONSTRAINT() != null ? constraint.id_()[0].GetText() : string.Empty, + ParentName = alterTableContext.children[2].GetText(), + ColumnNames = new List { constraint.forColumn.GetText() }, + Value = constraint.DEFAULT() != null ? constraint.default_value_column.GetText() : string.Empty }; } } diff --git a/SqlSchemaCompare.Core/TSql/TSqlParserUpdateListener.cs b/SqlSchemaCompare.Core/TSql/TSqlParserUpdateListener.cs index 832ee63..21b8922 100644 --- a/SqlSchemaCompare.Core/TSql/TSqlParserUpdateListener.cs +++ b/SqlSchemaCompare.Core/TSql/TSqlParserUpdateListener.cs @@ -121,7 +121,9 @@ public override void ExitCreate_type([NotNull] TSqlParser.Create_typeContext con public override void ExitCreate_index([NotNull] TSqlParser.Create_indexContext context) { - DbObjects.Add(_indexFactory.Create(context, _stream)); + var index = _indexFactory.Create(context, _stream); + var table = DbObjects.OfType().Single(x => x.Identifier == index.ParentName); + table.AddIndex(index as DbStructures.Index); } public override void ExitAlter_table([NotNull] TSqlParser.Alter_tableContext context) diff --git a/SqlSchemaCompare.Core/TSql/TSqlSchemaBuilder.cs b/SqlSchemaCompare.Core/TSql/TSqlSchemaBuilder.cs index 6916f0e..f655a3e 100644 --- a/SqlSchemaCompare.Core/TSql/TSqlSchemaBuilder.cs +++ b/SqlSchemaCompare.Core/TSql/TSqlSchemaBuilder.cs @@ -14,7 +14,9 @@ public string Build(DbObject dbObject, Operation operation, ResultProcessDbObjec return dbObject.DbObjectType switch { DbObjectType.Table => BuildCreateDropTable(dbObject as Table, operation), - DbObjectType.TableContraint => BuildTableConstraint(dbObject as Table.TableConstraint, operation, resultProcessDbObject), + DbObjectType.TableDefaultContraint => BuildTableConstraint(dbObject as TableConstraint, operation, resultProcessDbObject), + DbObjectType.TablePrimaryKeyContraint => BuildTableConstraint(dbObject as TableConstraint, operation, resultProcessDbObject), + DbObjectType.TableForeignKeyContraint => BuildTableConstraint(dbObject as TableConstraint, operation, resultProcessDbObject), DbObjectType.Column => BuildColumn(dbObject as Table.Column, operation), DbObjectType.View => BuildView(dbObject as View, operation), DbObjectType.StoreProcedure => BuildStoreProcedure(dbObject as StoreProcedure, operation), @@ -48,14 +50,14 @@ public string BuildSeparator() { return "GO\r\n"; } - private string BuildTableConstraint(Table.TableConstraint constraint, Operation operation, ResultProcessDbObject resultProcessDbObject) + private string BuildTableConstraint(TableConstraint constraint, Operation operation, ResultProcessDbObject resultProcessDbObject) { switch (operation) { case Operation.Create: - if (!resultProcessDbObject.GetDbObject(DbObjectType.Column, Operation.Create).Any(x => constraint.ColumnName.Contains(x.Name))) + if (!resultProcessDbObject.GetDbObject(DbObjectType.Column, Operation.Create).Any(x => constraint.ColumnNames.Contains(x.Name))) { - if (constraint.ConstraintType == Table.TableConstraint.ConstraintTypes.PrimaryKey) + if (constraint is Table.TablePrimaryKeyConstraint) { return $"ALTER TABLE {constraint.ParentName} ADD {constraint.Sql}"; } @@ -106,7 +108,7 @@ private string BuildColumn(Table.Column column, Operation operation) { case Operation.Create: var sql = $"ALTER TABLE {column.ParentName} ADD { column.Sql}"; - var constraintRelated = column.Table.Constraints.SingleOrDefault(x => x.ColumnName.Contains(column.Name) && x.ConstraintType == Table.TableConstraint.ConstraintTypes.Default); + var constraintRelated = column.Table.Constraints.OfType().SingleOrDefault(x => x.ColumnNames.Contains(column.Name)); if (constraintRelated != null) { sql = $"{sql} CONSTRAINT {constraintRelated.Name} DEFAULT {constraintRelated.Value}"; @@ -193,7 +195,7 @@ private string BuildIndex(Index dbObject, Operation operation) return operation switch { Operation.Create => dbObject.Sql, - Operation.Drop => $"DROP INDEX {dbObject.Identifier} ON {dbObject.TableName}", + Operation.Drop => $"DROP INDEX {dbObject.Identifier} ON {dbObject.ParentName}", _ => throw new NotSupportedException($"Operation not supported on TYPE"), }; } diff --git a/SqlSchemaCompare.Core/UpdateSchemaManager.cs b/SqlSchemaCompare.Core/UpdateSchemaManager.cs index 684ff44..64d8236 100644 --- a/SqlSchemaCompare.Core/UpdateSchemaManager.cs +++ b/SqlSchemaCompare.Core/UpdateSchemaManager.cs @@ -30,11 +30,16 @@ public UpdateSchemaManager(ISchemaBuilder schemaBuilder) ( DbObjectType.Schema, Operation.Create ), ( DbObjectType.Table, Operation.Create ), - ( DbObjectType.TableContraint, Operation.Drop), + ( DbObjectType.TableDefaultContraint, Operation.Drop), + ( DbObjectType.TableForeignKeyContraint, Operation.Drop), + ( DbObjectType.TablePrimaryKeyContraint, Operation.Drop), + ( DbObjectType.Index, Operation.Drop), ( DbObjectType.Column, Operation.Create), ( DbObjectType.Column, Operation.Drop ), ( DbObjectType.Column, Operation.Alter ), - ( DbObjectType.TableContraint, Operation.Create), + ( DbObjectType.TablePrimaryKeyContraint, Operation.Create), + ( DbObjectType.TableForeignKeyContraint, Operation.Create), + ( DbObjectType.TableDefaultContraint, Operation.Create), ( DbObjectType.Table, Operation.Drop ), ( DbObjectType.StoreProcedure, Operation.Drop), @@ -57,8 +62,7 @@ public UpdateSchemaManager(ISchemaBuilder schemaBuilder) ( DbObjectType.Type, Operation.Drop ), ( DbObjectType.Type, Operation.Create ), - - ( DbObjectType.Index, Operation.Drop ), + ( DbObjectType.Index, Operation.Create ), ( DbObjectType.Schema, Operation.Drop ), @@ -79,7 +83,6 @@ public string UpdateSchema(IEnumerable sourceObjects, IEnumerable(sourceObjects, destinationObjects, resultProcessDbObject, DbObjectType.View); ProcessTrigger(sourceObjects, destinationObjects, resultProcessDbObject); ProcessDbObjectWithoutAlter(sourceObjects, destinationObjects, resultProcessDbObject, DbObjectType.Type); - ProcdessIndex(sourceObjects, destinationObjects, resultProcessDbObject); StringBuilder updateSchemaStringBuild = new(); if (resultProcessDbObject.OperationsOnDbObject.Any()) @@ -132,8 +135,8 @@ private void ProcessMember(IEnumerable sourceObjects, IEnumerable roleNameDropped.Contains(x.RoleName))) .Except(destinationDb.Where(x => userNameDropped.Contains(x.Name))); - CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject, DbObjectType.Member); - DropDbObjectByName(originDb, destinationDb, resultProcessDbObject, DbObjectType.Member); + CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject); + DropDbObjectByName(originDb, destinationDb, resultProcessDbObject); } private void ProcessTrigger(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) @@ -154,8 +157,8 @@ private void ProcessUser(IEnumerable sourceObjects, IEnumerable(); var destinationDb = destinationObjects.OfType(); - var toCreate = CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject, DbObjectType.User); - DropDbObjectByName(originDb, destinationDb, resultProcessDbObject, DbObjectType.User); + var toCreate = CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject); + DropDbObjectByName(originDb, destinationDb, resultProcessDbObject); var toAlter = originDb .Except(toCreate) @@ -169,8 +172,8 @@ private void ProcessRole(IEnumerable sourceObjects, IEnumerable(); var destinationDb = destinationObjects.OfType(); - CreateDbObject(originDb, destinationDb, resultProcessDbObject, DbObjectType.Role); - DropDbObject(originDb, destinationDb, resultProcessDbObject, DbObjectType.Role); + CreateDbObject(originDb, destinationDb, resultProcessDbObject); + DropDbObject(originDb, destinationDb, resultProcessDbObject); } private void ProcessSchema(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) @@ -178,8 +181,8 @@ private void ProcessSchema(IEnumerable sourceObjects, IEnumerable(); var destinationDb = destinationObjects.OfType(); - CreateDbObject(originDb, destinationDb, resultProcessDbObject, DbObjectType.Schema); - DropDbObject(originDb, destinationDb, resultProcessDbObject, DbObjectType.Schema); + CreateDbObject(originDb, destinationDb, resultProcessDbObject); + DropDbObject(originDb, destinationDb, resultProcessDbObject); } private void ProcessTable(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) @@ -193,12 +196,13 @@ private void ProcessTable(IEnumerable sourceObjects, IEnumerable(tableOrigin, Operation.Create); } else { var destinationTable = destinationDb.Single(x => x.Identifier == tableOrigin.Identifier); - var constraintToCreate = CreateDbObject(tableOrigin.Constraints, destinationTable.Constraints, resultProcessDbObject, DbObjectType.Table); + var constraintToCreate = CreateDbObject(tableOrigin.Constraints, destinationTable.Constraints, resultProcessDbObject); var constraintToDrop = DropDbObject(tableOrigin.Constraints, destinationTable.Constraints) .GroupBy(x => x.Name) @@ -210,18 +214,28 @@ private void ProcessTable(IEnumerable sourceObjects, IEnumerable !destinationTable.Indexes.Contains(x)).ToList(); //discard object present in origin, present in destination and equals + + resultProcessDbObject.AddOperation(indexToAlter, Operation.Drop); + resultProcessDbObject.AddOperation(indexToAlter, Operation.Create); if (tableOrigin != destinationTable) { //columns different - ProcessTableColumn(tableOrigin, destinationTable, destinationObjects, resultProcessDbObject); + ProcessTableColumn(tableOrigin, destinationTable, resultProcessDbObject); } } } - DropDbObject(originDb, destinationDb, resultProcessDbObject, DbObjectType.Table); + DropDbObject(originDb, destinationDb, resultProcessDbObject); } - private void ProcessTableColumn(Table table, Table destinationTable, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) + private void ProcessTableColumn(Table table, Table destinationTable, ResultProcessDbObject resultProcessDbObject) { IList columnsToAdd; IList columnsToDrop; @@ -250,34 +264,28 @@ private void ProcessTableColumn(Table table, Table destinationTable, IEnumerable resultProcessDbObject.AddOperation(columnsToDrop, Operation.Drop); var columnsOfCostraintToDropAndCreate = destinationTable - .Constraints.SelectMany(x => x.ColumnName) + .Constraints.SelectMany(x => x.ColumnNames) .Intersect(columnsToAlter.Select(x => x.Name)); - - var constrainttoDropAndCreate = destinationTable.Constraints - .Where(x => columnsOfCostraintToDropAndCreate.Intersect(x.ColumnName).Any()) - .Except(resultProcessDbObject.GetDbObject(DbObjectType.TableContraint, Operation.Drop))//constraint that we have to drop - .ToList(); - resultProcessDbObject.AddOperation(constrainttoDropAndCreate, Operation.Drop); - resultProcessDbObject.AddOperation(constrainttoDropAndCreate, Operation.Create); - } - - private void ProcdessIndex(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) - { - var originDb = sourceObjects.OfType(); - var destinationDb = destinationObjects.OfType(); - var tableNameToDrop = resultProcessDbObject.GetDbObject(DbObjectType.Table, Operation.Drop).Select(x => x.Identifier); - destinationDb = destinationDb.Except(destinationDb.Where(x => tableNameToDrop.Contains(x.TableName))); + var constraintToDropAndCreate = destinationTable.Constraints + .Where(x => columnsOfCostraintToDropAndCreate.Intersect(x.ColumnNames).Any()) + .Except(resultProcessDbObject.GetDbObject(DbObjectType.TableDefaultContraint, Operation.Drop) + .Union(resultProcessDbObject.GetDbObject(DbObjectType.TableForeignKeyContraint, Operation.Drop)) + .Union(resultProcessDbObject.GetDbObject(DbObjectType.TablePrimaryKeyContraint, Operation.Drop))) //constraint that we have to drop + .ToList(); + resultProcessDbObject.AddOperation(constraintToDropAndCreate, Operation.Drop); + resultProcessDbObject.AddOperation(constraintToDropAndCreate, Operation.Create); - var toCreate = CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject, DbObjectType.Index); - DropDbObjectByName(originDb, destinationDb, resultProcessDbObject, DbObjectType.Index); + var columnsOfIndexToDropAndCreate = destinationTable + .Indexes.SelectMany(x => x.ColumnNames) + .Intersect(columnsToAlter.Select(x => x.Name)); - var toAlter = originDb - .Except(toCreate) - .Where(x => !destinationDb.Contains(x)).ToList(); //discard object present in origin, present in destination and equals - - resultProcessDbObject.AddOperation(toAlter, Operation.Drop); - resultProcessDbObject.AddOperation(toAlter, Operation.Create); + var indexToDropAndCreate = destinationTable.Indexes + .Where(x => columnsOfIndexToDropAndCreate.Intersect(x.ColumnNames).Any()) + .Except(resultProcessDbObject.GetDbObject(DbObjectType.Index, Operation.Drop))//constraint that we have to drop + .ToList(); + resultProcessDbObject.AddOperation(indexToDropAndCreate, Operation.Drop); + resultProcessDbObject.AddOperation(indexToDropAndCreate, Operation.Create); } private void ProcessDbObjectWithoutAlter(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject, DbObjectType dbObjectType) where T: DbObject @@ -285,8 +293,8 @@ private void ProcessDbObjectWithoutAlter(IEnumerable sourceObjects, var originDb = sourceObjects.OfType(); var destinationDb = destinationObjects.OfType(); - var toCreate = CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject, dbObjectType); - DropDbObjectByName(originDb, destinationDb, resultProcessDbObject, dbObjectType); + var toCreate = CreateDbObjectByName(originDb, destinationDb, resultProcessDbObject); + DropDbObjectByName(originDb, destinationDb, resultProcessDbObject); var toAlter = originDb .Except(toCreate) @@ -302,8 +310,8 @@ private void ProcessDbObjectWithoutAlter(IEnumerable sourceObjects, var originDb = sourceObjects.OfType(); var destinationDb = destinationObjects.OfType(); - var toCreate = CreateDbObject(originDb, destinationDb, resultProcessDbObject, dbObjectType); - var toDrop = DropDbObject(originDb, destinationDb, resultProcessDbObject, dbObjectType); + var toCreate = CreateDbObject(originDb, destinationDb, resultProcessDbObject); + var toDrop = DropDbObject(originDb, destinationDb, resultProcessDbObject); var toAlter = originDb .Except(toCreate) @@ -313,7 +321,7 @@ private void ProcessDbObjectWithoutAlter(IEnumerable sourceObjects, return (toCreate, toAlter, toDrop); } - private List CreateDbObject(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject, DbObjectType dbObjectType) where T : DbObject + private List CreateDbObject(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) where T : DbObject { var toCreate = sourceObjects .Where(dbObject => sourceObjects @@ -324,7 +332,7 @@ private List CreateDbObject(IEnumerable sourceObjects, IEnumerable d resultProcessDbObject.AddOperation(toCreate, Operation.Create); return toCreate; } - private List CreateDbObjectByName(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject, DbObjectType dbObjectType) where T : DbObject + private List CreateDbObjectByName(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) where T : DbObject { var toCreate = sourceObjects .Where(dbObject => sourceObjects @@ -335,7 +343,7 @@ private List CreateDbObjectByName(IEnumerable sourceObjects, IEnumerabl resultProcessDbObject.AddOperation(toCreate, Operation.Create); return toCreate; } - private List DropDbObject(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject, DbObjectType dbObjectType) where T : DbObject + private List DropDbObject(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) where T : DbObject { var toDrop = DropDbObject(sourceObjects, destinationObjects); resultProcessDbObject.AddOperation(toDrop, Operation.Drop); @@ -351,7 +359,7 @@ private List DropDbObject(IEnumerable sourceObjects, IEnumerable des .Contains(dbObject.Identifier)) // object with completeName to be dropped .ToList(); } - private void DropDbObjectByName(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject, DbObjectType dbObjectType) where T : DbObject + private void DropDbObjectByName(IEnumerable sourceObjects, IEnumerable destinationObjects, ResultProcessDbObject resultProcessDbObject) where T : DbObject { var toDrop = destinationObjects .Where(dbObject => destinationObjects diff --git a/SqlSchemaCompare.Test/TSql/TSqlIndexTest.cs b/SqlSchemaCompare.Test/TSql/TSqlIndexTest.cs index 0157395..8797c01 100644 --- a/SqlSchemaCompare.Test/TSql/TSqlIndexTest.cs +++ b/SqlSchemaCompare.Test/TSql/TSqlIndexTest.cs @@ -19,20 +19,28 @@ public TSqlIndexTest() [Fact] public void CreateIndex() { - const string sql = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] + const string sql = +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( - [ID] ASC -)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable]"; + [ID] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] +GO"; var objectFactory = new TSqlObjectFactory(); (var objects, var errors) = objectFactory.CreateObjectsForUpdateOperation(sql); - var dbobject = objects.Single() as Index; + var table = objects.Single() as Table; - dbobject.Name.ShouldBe("[indexName]"); - dbobject.Schema.ShouldBeEmpty(); - dbobject.Identifier.ShouldBe("[indexName]"); - dbobject.Sql.ShouldBe(sql); + table.Indexes.Single().Name.ShouldBe("[indexName]"); + table.Indexes.Single().Schema.ShouldBeEmpty(); + table.Indexes.Single().Identifier.ShouldBe("[indexName]"); + table.Indexes.Single().Sql.ShouldBe( +@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +( + [ID] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable]"); errors.Count().ShouldBe(0); } @@ -56,13 +64,19 @@ public void UpdateSchemaEqualsDbObject() // Expect updateSchema should be empty const string origin = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] GO"; const string destination = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] @@ -81,7 +95,10 @@ public void UpdateSchemaCreateDbObject() // Expect updateSchema contains create statement const string origin = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] @@ -91,7 +108,10 @@ [ID] ASC (string updateSchema, string errors) = UtilityTest.UpdateSchema(origin, destination, SelectedObjects); updateSchema.ShouldBe( -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] @@ -107,11 +127,14 @@ public void UpdateSchemaDropDbObject() // When present db object in destination absent from origin // Expect updateSchema contains drop statement - const string origin = ""; + const string origin = "CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL)"; const string destination = @"CREATE DATABASE [dbName] GO +CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC @@ -138,13 +161,19 @@ public void UpdateSchemaAlterDbObject() // Expect updateSchema contains alter statement const string origin = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] GO"; const string destination = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC, [par] @@ -168,13 +197,16 @@ [ID] ASC } [Theory] - [MemberData(nameof(TestDbObjectGenerator.ListDbObjectTypeExceptOne), new DbObjectType[] { DbObjectType.Index }, MemberType = typeof(TestDbObjectGenerator))] + [MemberData(nameof(TestDbObjectGenerator.ListDbObjectTypeExceptOne), new DbObjectType[] { DbObjectType.Index, DbObjectType.Table }, MemberType = typeof(TestDbObjectGenerator))] public void UpdateSchemaNotSelectedDbObject(DbObjectType dbObjectTypes) { // When user not select Index db object, update schema is created without Index const string origin = -@"CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] +@"CREATE TABLE [dbo].[table] ([ID] [INT] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [indexName] ON [dbo].[table] ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [indexTable] diff --git a/SqlSchemaCompare.Test/TSql/TSqlTableTest.cs b/SqlSchemaCompare.Test/TSql/TSqlTableTest.cs index 69f59c8..fca3b6b 100644 --- a/SqlSchemaCompare.Test/TSql/TSqlTableTest.cs +++ b/SqlSchemaCompare.Test/TSql/TSqlTableTest.cs @@ -466,16 +466,66 @@ ALTER TABLE [dbo].[tbl] ALTER COLUMN [column1] [int] NOT NULL ALTER TABLE [dbo].[tblLookup] ALTER COLUMN [ID] [int] NOT NULL GO -ALTER TABLE [dbo].[tbl] WITH CHECK ADD CONSTRAINT [FK_constraint] FOREIGN KEY([column1]) -REFERENCES [dbo].[tblLookup] ([ID]) -GO - ALTER TABLE [dbo].[tblLookup] ADD CONSTRAINT [PK] PRIMARY KEY CLUSTERED ( [ID] ASC, [description] DESC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [db] GO +ALTER TABLE [dbo].[tbl] WITH CHECK ADD CONSTRAINT [FK_constraint] FOREIGN KEY([column1]) +REFERENCES [dbo].[tblLookup] ([ID]) +GO + +"); + errors.ShouldBeEmpty(); + } + + [Fact] + public void DropIndexBeforeAlterColumn() + { + const string origin = +@"CREATE TABLE [dbo].[tbl]( + [ID] [int] IDENTITY(1,1) NOT NULL, + [column1] [int] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [idx] ON [dbo].[tbl] +( + [column1] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [db] +GO + +"; + + const string destination = +@"CREATE TABLE [dbo].[tbl]( + [ID] [int] IDENTITY(1,1) NOT NULL, + [column1] [tinyint] NOT NULL) +GO + +CREATE NONCLUSTERED INDEX [idx] ON [dbo].[tbl] +( + [column1] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [db] +GO + +"; + + (string updateSchema, string errors) = UtilityTest.UpdateSchema(origin, destination, SelectedObjects); + + updateSchema.ShouldBe( +@"DROP INDEX [idx] ON [dbo].[tbl] +GO + +ALTER TABLE [dbo].[tbl] ALTER COLUMN [column1] [int] NOT NULL +GO + +CREATE NONCLUSTERED INDEX [idx] ON [dbo].[tbl] +( + [column1] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [db] +GO + "); errors.ShouldBeEmpty(); } @@ -548,7 +598,7 @@ ALTER TABLE [schema].[tbl] ADD CONSTRAINT [constraint] DEFAULT (getdate()) FOR [ [Theory] - [MemberData(nameof(TestDbObjectGenerator.ListDbObjectTypeExceptOne), new DbObjectType[] { DbObjectType.Table, DbObjectType.Column, DbObjectType.TableContraint }, MemberType = typeof(TestDbObjectGenerator))] + [MemberData(nameof(TestDbObjectGenerator.ListDbObjectTypeExceptOne), new DbObjectType[] { DbObjectType.Table, DbObjectType.Column, DbObjectType.TablePrimaryKeyContraint, DbObjectType.TableDefaultContraint, DbObjectType.TableForeignKeyContraint }, MemberType = typeof(TestDbObjectGenerator))] public void UpdateSchemaNotSelectedDbObject(DbObjectType dbObjectTypes) { // When user not select table db object, update schema is created without table diff --git a/SqlSchemaCompare.WindowsForm/MainForm.Designer.cs b/SqlSchemaCompare.WindowsForm/MainForm.Designer.cs index 987a630..599dfc1 100644 --- a/SqlSchemaCompare.WindowsForm/MainForm.Designer.cs +++ b/SqlSchemaCompare.WindowsForm/MainForm.Designer.cs @@ -58,7 +58,6 @@ private void InitializeComponent() this.ofdUpdateSchemaFile = new System.Windows.Forms.OpenFileDialog(); this.GrpDbObjects = new System.Windows.Forms.GroupBox(); this.ChkOther = new System.Windows.Forms.CheckBox(); - this.ChkIndex = new System.Windows.Forms.CheckBox(); this.ChkTrigger = new System.Windows.Forms.CheckBox(); this.ChkTableType = new System.Windows.Forms.CheckBox(); this.ChkSchema = new System.Windows.Forms.CheckBox(); @@ -353,7 +352,6 @@ private void InitializeComponent() // GrpDbObjects // this.GrpDbObjects.Controls.Add(this.ChkOther); - this.GrpDbObjects.Controls.Add(this.ChkIndex); this.GrpDbObjects.Controls.Add(this.ChkTrigger); this.GrpDbObjects.Controls.Add(this.ChkTableType); this.GrpDbObjects.Controls.Add(this.ChkSchema); @@ -377,25 +375,13 @@ private void InitializeComponent() this.ChkOther.AutoSize = true; this.ChkOther.Checked = true; this.ChkOther.CheckState = System.Windows.Forms.CheckState.Checked; - this.ChkOther.Location = new System.Drawing.Point(6, 336); + this.ChkOther.Location = new System.Drawing.Point(7, 306); this.ChkOther.Name = "ChkOther"; this.ChkOther.Size = new System.Drawing.Size(114, 24); this.ChkOther.TabIndex = 10; this.ChkOther.Text = "Other object"; this.ChkOther.UseVisualStyleBackColor = true; // - // ChkIndex - // - this.ChkIndex.AutoSize = true; - this.ChkIndex.Checked = true; - this.ChkIndex.CheckState = System.Windows.Forms.CheckState.Checked; - this.ChkIndex.Location = new System.Drawing.Point(6, 306); - this.ChkIndex.Name = "ChkIndex"; - this.ChkIndex.Size = new System.Drawing.Size(67, 24); - this.ChkIndex.TabIndex = 8; - this.ChkIndex.Text = "Index"; - this.ChkIndex.UseVisualStyleBackColor = true; - // // ChkTrigger // this.ChkTrigger.AutoSize = true; @@ -583,7 +569,6 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox ChkTable; private System.Windows.Forms.CheckBox ChkAll; private System.Windows.Forms.CheckBox ChkOther; - private System.Windows.Forms.CheckBox ChkIndex; private System.Windows.Forms.CheckBox ChkTrigger; private System.Windows.Forms.CheckBox ChkTableType; private System.Windows.Forms.CheckBox ChkSchema; diff --git a/SqlSchemaCompare.WindowsForm/MainForm.cs b/SqlSchemaCompare.WindowsForm/MainForm.cs index 9472ee1..3e6bcaf 100644 --- a/SqlSchemaCompare.WindowsForm/MainForm.cs +++ b/SqlSchemaCompare.WindowsForm/MainForm.cs @@ -319,7 +319,6 @@ private void ChkAll_CheckedChanged(object sender, EventArgs e) ChkTrigger.Checked = ChkAll.Checked; ChkTableType.Checked = ChkAll.Checked; ChkOther.Checked = ChkAll.Checked; - ChkIndex.Checked = ChkAll.Checked; } private IEnumerable SelectedObjectType() @@ -343,8 +342,6 @@ private IEnumerable SelectedObjectType() selectedObjectType.AddRange(relatedDbObjectsConfiguration.GetRelatedDbObjects(DbObjectType.Type)); if (ChkOther.Checked) selectedObjectType.AddRange(relatedDbObjectsConfiguration.GetRelatedDbObjects(DbObjectType.Other)); - if (ChkIndex.Checked) - selectedObjectType.AddRange(relatedDbObjectsConfiguration.GetRelatedDbObjects(DbObjectType.Index)); return selectedObjectType; } diff --git a/SqlSchemaCompare.WindowsForm/RelatedDbObjects.cs b/SqlSchemaCompare.WindowsForm/RelatedDbObjects.cs deleted file mode 100644 index 69ad8f1..0000000 --- a/SqlSchemaCompare.WindowsForm/RelatedDbObjects.cs +++ /dev/null @@ -1,28 +0,0 @@ -using SqlSchemaCompare.Core.DbStructures; -using System.Collections.Generic; -using System.Linq; - -namespace SqlSchemaCompare.WindowsForm -{ - public class RelatedDbObjectsConfiguration - { - public static List> RelatedDbObjects = new List> - { - new List { DbObjectType.Function }, - new List { DbObjectType.StoreProcedure }, - new List { DbObjectType.Table, DbObjectType.TableContraint, DbObjectType.Column }, - new List { DbObjectType.User, DbObjectType.Role, DbObjectType.Member }, - new List { DbObjectType.View }, - new List { DbObjectType.Schema }, - new List { DbObjectType.Trigger, DbObjectType.EnableTrigger }, - new List { DbObjectType.Type }, - new List { DbObjectType.Other}, - new List { DbObjectType.Index } - }; - - public List GetRelatedDbObjects(DbObjectType dbObject) - { - return RelatedDbObjects.Single(x => x.Contains(dbObject)); - } - } -} diff --git a/SqlSchemaCompare.WindowsForm/SqlSchemaCompare.WindowsForm.csproj b/SqlSchemaCompare.WindowsForm/SqlSchemaCompare.WindowsForm.csproj index 57b0e6f..3f56da3 100644 --- a/SqlSchemaCompare.WindowsForm/SqlSchemaCompare.WindowsForm.csproj +++ b/SqlSchemaCompare.WindowsForm/SqlSchemaCompare.WindowsForm.csproj @@ -11,7 +11,7 @@ true true win-x86;win-x64 - 1.0.17 + 1.0.18