diff --git a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs index 88186463b8..b23c75aa4b 100644 --- a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs @@ -42,9 +42,9 @@ namespace Umbraco.Core.Models.Rdbms //[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "url, createDateUtc")] public string Url { get; set; } - [Column("hurl")] + [Column("urlHash")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "hurl, contentKey, createDateUtc")] - public string Hurl { get; set; } + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, createDateUtc")] + public string UrlHash { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs new file mode 100644 index 0000000000..f6a603c70f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Text; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter; +using Umbraco.Core.Persistence.Migrations.Syntax.Create; +using Umbraco.Core.Persistence.Migrations.Syntax.Delete; +using Umbraco.Core.Persistence.Migrations.Syntax.Execute; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations +{ + internal class LocalMigrationContext : MigrationContext + { + private readonly ISqlSyntaxProvider _sqlSyntax; + + public LocalMigrationContext(DatabaseProviders databaseProvider, Database database, ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(databaseProvider, database, logger) + { + _sqlSyntax = sqlSyntax; + } + + public IExecuteBuilder Execute + { + get { return new ExecuteBuilder(this, _sqlSyntax); } + } + + public IDeleteBuilder Delete + { + get { return new DeleteBuilder(this, _sqlSyntax); } + } + + public IAlterSyntaxBuilder Alter + { + get { return new AlterSyntaxBuilder(this, _sqlSyntax); } + } + + public ICreateBuilder Create + { + get { return new CreateBuilder(this, _sqlSyntax); } + } + + public string GetSql() + { + var sb = new StringBuilder(); + foreach (var sql in Expressions.Select(x => x.Process(Database))) + { + sb.Append(sql); + sb.AppendLine(); + sb.AppendLine("GO"); + } + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index 56e423ebaf..d667d2d91f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -1,6 +1,7 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations.Syntax.Create; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero @@ -14,24 +15,31 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer public override void Up() { - // don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoRedirectUrl")) return; + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } - Create.Table("umbracoRedirectUrl") + private string MigrationCode(Database database) + { + // don't execute if the table is already there + var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); + if (tables.InvariantContains("umbracoRedirectUrl")) return null; + + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + + localContext.Create.Table("umbracoRedirectUrl") .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoRedirectUrl") .WithColumn("contentId").AsInt32().NotNullable() .WithColumn("createDateUtc").AsDateTime().NotNullable() .WithColumn("url").AsString(2048).NotNullable(); - //Create.PrimaryKey("PK_umbracoRedirectUrl").OnTable("umbracoRedirectUrl").Columns(new[] { "id" }); + localContext.Create.Index("IX_umbracoRedirectUrl") + .OnTable("umbracoRedirectUrl") + .OnColumn("url").Ascending() + .OnColumn("createDateUtc").Ascending() + .WithOptions().NonClustered(); - Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") - .OnColumn("url") - .Ascending() - .OnColumn("createDateUtc") - .Ascending() - .WithOptions().NonClustered(); + return localContext.GetSql(); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs index 418c5ded91..1f204192e1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs @@ -1,6 +1,10 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter; +using Umbraco.Core.Persistence.Migrations.Syntax.Create; +using Umbraco.Core.Persistence.Migrations.Syntax.Delete; +using Umbraco.Core.Persistence.Migrations.Syntax.Execute; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero @@ -14,21 +18,37 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer public override void Up() { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(Database database) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("contentKey"))) - return; + return null; - Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - Delete.Column("contentId").FromTable("umbracoRedirectUrl"); + localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field - Alter.Table("umbracoRedirectUrl") - .AddColumn("contentKey").AsGuid().NotNullable(); + localContext.Delete.Column("contentId").FromTable("umbracoRedirectUrl"); - Create.ForeignKey("FK_umbracoRedirectUrl") + // SQL CE does not want to alter-add non-nullable columns ;-( + // but it's OK to create as nullable then alter, go figure + //localContext.Alter.Table("umbracoRedirectUrl") + // .AddColumn("contentKey").AsGuid().NotNullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AddColumn("contentKey").AsGuid().Nullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AlterColumn("contentKey").AsGuid().NotNullable(); + + localContext.Create.ForeignKey("FK_umbracoRedirectUrl") .FromTable("umbracoRedirectUrl").ForeignColumn("contentKey") .ToTable("umbracoNode").PrimaryColumn("uniqueID"); + + return localContext.GetSql(); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs index a632ce2fc5..a9550301b2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs @@ -14,19 +14,32 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer public override void Up() { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + private string MigrationCode(Database database) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("hurl"))) - return; + return null; - Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); + localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field - Alter.Table("umbracoRedirectUrl") - .AddColumn("hurl").AsString(16).NotNullable(); + localContext.Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); - Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") + // SQL CE does not want to alter-add non-nullable columns ;-( + // but it's OK to create as nullable then alter, go figure + //localContext.Alter.Table("umbracoRedirectUrl") + // .AddColumn("urlHash").AsString(16).NotNullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AddColumn("hurl").AsString(16).Nullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AlterColumn("hurl").AsString(16).NotNullable(); + + localContext.Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") .OnColumn("hurl") .Ascending() .OnColumn("contentKey") @@ -34,6 +47,8 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer .OnColumn("createDateUtc") .Descending() .WithOptions().NonClustered(); + + return localContext.GetSql(); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs new file mode 100644 index 0000000000..e4724fc48f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs @@ -0,0 +1,59 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + [Migration("7.5.0", 103, GlobalSettings.UmbracoMigrationName)] + public class AddRedirectUrlTable4 : MigrationBase + { + public AddRedirectUrlTable4(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + private string MigrationCode(Database database) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("urlHash"))) + return null; + + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + + localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + + localContext.Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); + + localContext.Delete.Column("hurl").FromTable("umbracoRedirectUrl"); + + // SQL CE does not want to alter-add non-nullable columns ;-( + // but it's OK to create as nullable then alter, go figure + //localContext.Alter.Table("umbracoRedirectUrl") + // .AddColumn("urlHash").AsString(16).NotNullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AddColumn("urlHash").AsString(16).Nullable(); + localContext.Alter.Table("umbracoRedirectUrl") + .AlterColumn("urlHash").AsString(16).NotNullable(); + + localContext.Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() + .WithOptions().NonClustered(); + + return localContext.GetSql(); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 5a375a87c9..3cde41fd18 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -105,7 +105,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); ContentKey = redirectUrl.ContentKey, CreateDateUtc = redirectUrl.CreateDateUtc, Url = redirectUrl.Url, - Hurl = HashUrl(redirectUrl.Url) + UrlHash = HashUrl(redirectUrl.Url) }; } @@ -132,8 +132,8 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); public IRedirectUrl Get(string url, Guid contentKey) { - var hurl = HashUrl(url); - var sql = GetBaseQuery(false).Where(x => x.Url == url && x.Hurl == hurl && x.ContentKey == contentKey); + var urlHash = HashUrl(url); + var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey); var dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : Map(dto); } @@ -155,9 +155,9 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); public IRedirectUrl GetMostRecentUrl(string url) { - var hurl = HashUrl(url); + var urlHash = HashUrl(url); var sql = GetBaseQuery(false) - .Where(x => x.Url == url && x.Hurl == hurl) + .Where(x => x.Url == url && x.UrlHash == urlHash) .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); var dtos = Database.Fetch(sql); var dto = dtos.FirstOrDefault(); @@ -196,10 +196,10 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); private static string HashUrl(string url) { - var h = new MD5CryptoServiceProvider(); - var i = Encoding.UTF8.GetBytes(url); - var o = h.ComputeHash(i); - return Encoding.UTF8.GetString(o); + var crypto = new MD5CryptoServiceProvider(); + var inputBytes = Encoding.UTF8.GetBytes(url); + var hashedBytes = crypto.ComputeHash(inputBytes); + return Encoding.UTF8.GetString(hashedBytes); } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ce7676fbc0..06e9014d31 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -425,7 +425,9 @@ + + diff --git a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs index 55d60eed66..872495dd7f 100644 --- a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs @@ -1,10 +1,20 @@ using System; using System.Linq; +using Moq; using NUnit.Framework; +using Semver; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Migrations { @@ -90,5 +100,48 @@ namespace Umbraco.Tests.Migrations Assert.AreEqual("[{\"title\":\"\",\"caption\":\"\",\"link\":\"\",\"newWindow\":false,\"type\":\"external\",\"internal\":null,\"edit\":false,\"isInternal\":false}]", data.Text); } + + [Test] + public void Issue8361Test() + { + var logger = new DebugDiagnosticsLogger(); + + //Setup the MigrationRunner + var migrationRunner = new MigrationRunner( + Mock.Of(), + logger, + new SemVersion(7, 4, 0), + new SemVersion(7, 5, 0), + GlobalSettings.UmbracoMigrationName, + + //pass in explicit migrations + new DeleteRedirectUrlTable(SqlSyntax, logger), + new AddRedirectUrlTable(SqlSyntax, logger), + new AddRedirectUrlTable2(SqlSyntax, logger), + new AddRedirectUrlTable3(SqlSyntax, logger), + new AddRedirectUrlTable4(SqlSyntax, logger) + ); + + var db = new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Logger); + + var upgraded = migrationRunner.Execute(db, DatabaseProviders.SqlServerCE, true); + Assert.IsTrue(upgraded); + } + + [Migration("7.5.0", 99, GlobalSettings.UmbracoMigrationName)] + public class DeleteRedirectUrlTable : MigrationBase + { + public DeleteRedirectUrlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + Delete.Table("umbracoRedirectUrl"); + } + + public override void Down() + { } + } } } \ No newline at end of file