From b66412e2d65337fe05dd8690c6e34fd18fcdc5ea Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 1 Nov 2017 15:35:37 +0100 Subject: [PATCH] Complete PropertyData table migration --- .../Models/Rdbms/PropertyDataDto.cs | 2 +- .../Migrations/Syntax/Create/CreateBuilder.cs | 8 +-- .../Execute/ExecuteBuilderExtensions.cs | 24 ++++---- .../TargetVersionEight/VariantsMigration.cs | 60 ++++++++++++++++--- .../Migrations/AdvancedMigrationTests.cs | 55 +++++++++++++++++ 5 files changed, 123 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 54425dbf75..2ab13d5a55 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Models.Rdbms [ForeignKey(typeof(LanguageDto))] [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_LanguageId")] [NullSetting(NullSetting = NullSettings.Null)] - public int LanguageId { get; set; } + public int? LanguageId { get; set; } [Column("segment")] [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Segment")] diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs index b29c8c8884..165f771ff2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs @@ -43,10 +43,10 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create var tableDefinition = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); AddSql(SqlSyntax.FormatPrimaryKey(tableDefinition)); - foreach (var sql in SqlSyntax.Format(tableDefinition.ForeignKeys)) - AddSql(sql); foreach (var sql in SqlSyntax.Format(tableDefinition.Indexes)) AddSql(sql); + foreach (var sql in SqlSyntax.Format(tableDefinition.ForeignKeys)) + AddSql(sql); } public void KeysAndIndexes(Type typeOfDto) @@ -54,10 +54,10 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create var tableDefinition = DefinitionFactory.GetTableDefinition(typeOfDto, SqlSyntax); AddSql(SqlSyntax.FormatPrimaryKey(tableDefinition)); - foreach (var sql in SqlSyntax.Format(tableDefinition.ForeignKeys)) - AddSql(sql); foreach (var sql in SqlSyntax.Format(tableDefinition.Indexes)) AddSql(sql); + foreach (var sql in SqlSyntax.Format(tableDefinition.ForeignKeys)) + AddSql(sql); } private void AddSql(string sql) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilderExtensions.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilderExtensions.cs index a015ad3c7e..8fdd13a32d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilderExtensions.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilderExtensions.cs @@ -19,18 +19,18 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute { var local = context.GetLocalMigration(); - // drop indexes - var indexes = context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(context.Database).ToArray(); - foreach (var index in indexes.Where(x => x.TableName == tableName)) - local.Delete.Index(index.IndexName).OnTable(index.TableName); - // drop keys - var keys = context.SqlContext.SqlSyntax.GetConstraintsPerTable(context.Database).ToArray(); + var keys = context.SqlContext.SqlSyntax.GetConstraintsPerTable(context.Database).DistinctBy(x => x.Item2).ToArray(); foreach (var key in keys.Where(x => x.Item1 == tableName && x.Item2.StartsWith("FK_"))) local.Delete.ForeignKey(key.Item2).OnTable(key.Item1); foreach (var key in keys.Where(x => x.Item1 == tableName && x.Item2.StartsWith("PK_"))) local.Delete.PrimaryKey(key.Item2).FromTable(key.Item1); + // drop indexes + var indexes = context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(context.Database).DistinctBy(x => x.IndexName).ToArray(); + foreach (var index in indexes.Where(x => x.TableName == tableName)) + local.Delete.Index(index.IndexName).OnTable(index.TableName); + return local.GetSql(); } @@ -38,18 +38,18 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute { var local = context.GetLocalMigration(); - // drop indexes - var indexes = context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(context.Database).ToArray(); - foreach (var index in indexes) - local.Delete.Index(index.IndexName).OnTable(index.TableName); - // drop keys - var keys = context.SqlContext.SqlSyntax.GetConstraintsPerTable(context.Database).ToArray(); + var keys = context.SqlContext.SqlSyntax.GetConstraintsPerTable(context.Database).DistinctBy(x => x.Item2).ToArray(); foreach (var key in keys.Where(x => x.Item2.StartsWith("FK_"))) local.Delete.ForeignKey(key.Item2).OnTable(key.Item1); foreach (var key in keys.Where(x => x.Item2.StartsWith("PK_"))) local.Delete.PrimaryKey(key.Item2).FromTable(key.Item1); + // drop indexes + var indexes = context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(context.Database).DistinctBy(x => x.IndexName).ToArray(); + foreach (var index in indexes) + local.Delete.Index(index.IndexName).OnTable(index.TableName); + return local.GetSql(); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs index 1fe9045ef4..97c282d6ae 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/VariantsMigration.cs @@ -1,5 +1,10 @@ -using System.Linq; +using System; +using System.Linq; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Initial; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table; using Umbraco.Core.Persistence.Migrations.Syntax.Execute; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight @@ -33,15 +38,17 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight // add column propertyData.languageId if (!ColumnExists(PreTables.PropertyData, "languageId")) - Alter.Table(PreTables.PropertyData).AddColumn("languageId").AsInt32().Nullable(); + AddColumn(PreTables.PropertyData, "languageId"); // add column propertyData.segment if (!ColumnExists(PreTables.PropertyData, "segment")) - Alter.Table(PreTables.PropertyData).AddColumn("segment").AsString(256).Nullable(); + AddColumn(PreTables.PropertyData, "segment"); + + // do NOT use Rename.Column as it's borked on SQLCE - use ReplaceColumn instead // rename column propertyData.contentNodeId to nodeId if (ColumnExists(PreTables.PropertyData, "contentNodeId")) - Rename.Column("contentNodeId").OnTable(PreTables.PropertyData).To("nodeId"); + ReplaceColumn(PreTables.PropertyData, "contentNodeId", "nodeId"); // rename column propertyData.dataNtext to textValue // rename column propertyData.dataNvarchar to varcharValue @@ -49,15 +56,15 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight // rename column propertyData.dataInt to intValue // rename column propertyData.dataDate to dateValue if (ColumnExists(PreTables.PropertyData, "dataNtext")) - Rename.Column("dataNtext").OnTable(PreTables.PropertyData).To("textValue"); + ReplaceColumn(PreTables.PropertyData, "dataNtext", "textValue"); if (ColumnExists(PreTables.PropertyData, "dataNvarchar")) - Rename.Column("dataNtext").OnTable(PreTables.PropertyData).To("varcharValue"); + ReplaceColumn(PreTables.PropertyData, "dataNvarchar", "varcharValue"); if (ColumnExists(PreTables.PropertyData, "dataDecimal")) - Rename.Column("dataDecimal").OnTable(PreTables.PropertyData).To("decimalValue"); + ReplaceColumn(PreTables.PropertyData, "dataDecimal", "decimalValue"); if (ColumnExists(PreTables.PropertyData, "dataInt")) - Rename.Column("dataInt").OnTable(PreTables.PropertyData).To("intValue"); + ReplaceColumn(PreTables.PropertyData, "dataInt", "intValue"); if (ColumnExists(PreTables.PropertyData, "dataDate")) - Rename.Column("dataDate").OnTable(PreTables.PropertyData).To("dateValue"); + ReplaceColumn(PreTables.PropertyData, "dataDate", "dateValue"); // rename table Rename.Table(PreTables.PropertyData).To(Constants.DatabaseSchema.Tables.PropertyData); @@ -128,6 +135,41 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight public const string TaskType = "cmsTaskType"; } + private void AddColumn(string tableName, string columnName) + { + AddColumn(tableName, columnName, out var notNull); + if (notNull != null) Execute.Sql(notNull); + } + + private void AddColumn(string tableName, string columnName, out string notNull) + { + if (ColumnExists(tableName, columnName)) + throw new InvalidOperationException($"Column {tableName}.{columnName} already exists."); + + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + var column = table.Columns.First(x => x.Name == columnName); + var create = SqlSyntax.Format(column); + // some db cannot add a NOT NULL column, so change it into NULL + if (create.Contains("NOT NULL")) + { + notNull = string.Format(SqlSyntax.AlterColumn, SqlSyntax.GetQuotedTableName(tableName), create); + create = create.Replace("NOT NULL", "NULL"); + } + else + { + notNull = null; + } + Execute.Sql($"ALTER TABLE {SqlSyntax.GetQuotedTableName(tableName)} ADD COLUMN " + create); + } + + private void ReplaceColumn(string tableName, string currentName, string newName) + { + AddColumn(tableName, newName, out var notNull); + Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}"); + if (notNull != null) Execute.Sql(notNull); + Delete.Column(currentName).FromTable(tableName); + } + private bool TableExists(string tableName) { var tables = SqlSyntax.GetTablesInSchema(Context.Database); diff --git a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs index 5e930d8d09..98be81e286 100644 --- a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs @@ -5,9 +5,11 @@ using Semver; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Initial; using Umbraco.Core.Persistence.Migrations.Syntax.Execute; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; @@ -148,6 +150,38 @@ namespace Umbraco.Tests.Migrations } } + [Test] + public void CreateColumn() + { + var logger = new DebugDiagnosticsLogger(); + + using (var scope = ScopeProvider.CreateScope()) + { + var database = scope.Database; + + var context = new MigrationContext(database, logger); + + var runner = new MigrationRunner( + Mock.Of(), + Mock.Of(), + logger, + new SemVersion(0), // 0.0.0 + new SemVersion(1), // 1.0.0 + "Test", + + // explicit migrations + new CreateTableOfTDtoMigration(context), + new CreateColumnMigration(context) + ); + + var upgraded = runner.Execute(context); + Assert.IsTrue(upgraded); + + scope.Complete(); + } + + } + [Migration("1.0.0", 0, "Test")] public class CreateTableOfTDtoMigration : MigrationBase { @@ -212,5 +246,26 @@ namespace Umbraco.Tests.Migrations } } } + + [Migration("1.0.0", 2, "Test")] + public class CreateColumnMigration : MigrationBase + { + public CreateColumnMigration(IMigrationContext context) + : base(context) + { } + + public override void Up() + { + // cannot delete the column without this, of course + Execute.DropKeysAndIndexes(); + + Delete.Column("id").FromTable("umbracoNode"); + + var table = DefinitionFactory.GetTableDefinition(typeof(NodeDto), SqlSyntax); + var column = table.Columns.First(x => x.Name == "id"); + var create = SqlSyntax.Format(column); // returns [id] INTEGER NOT NULL IDENTITY(1060,1) + Execute.Sql($"ALTER TABLE {SqlSyntax.GetQuotedTableName("umbracoNode")} ADD COLUMN " + create); + } + } } }