diff --git a/src/Geta.NotFoundHandler.Optimizely/Data/SqlContentUrlHistoryRepository.cs b/src/Geta.NotFoundHandler.Optimizely/Data/SqlContentUrlHistoryRepository.cs index a87e4992..5905597c 100644 --- a/src/Geta.NotFoundHandler.Optimizely/Data/SqlContentUrlHistoryRepository.cs +++ b/src/Geta.NotFoundHandler.Optimizely/Data/SqlContentUrlHistoryRepository.cs @@ -9,13 +9,15 @@ using Geta.NotFoundHandler.Optimizely.Core.AutomaticRedirects; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using System.Security.Cryptography; +using System.Text; namespace Geta.NotFoundHandler.Optimizely.Data { public class SqlContentUrlHistoryRepository : IRepository, IContentUrlHistoryLoader { private const string ContentUrlHistoryTable = "[dbo].[NotFoundHandler.ContentUrlHistory]"; - private const string AllFields = "Id, ContentKey, Urls, CreatedUtc"; + private const string AllFields = "Id, ContentKey, Urls, CreatedUtc, md5_ContentKey"; private readonly IDataExecutor _dataExecutor; public SqlContentUrlHistoryRepository(IDataExecutor dataExecutor) @@ -35,33 +37,46 @@ private static JsonSerializerSettings JsonSettings return settings; } } + + private static byte[] CalculateMd5Hash(string input) + { + using var md5 = MD5.Create(); + var inputBytes = Encoding.Unicode.GetBytes(input); + var hashBytes = md5.ComputeHash(inputBytes); + + return hashBytes; + } public bool IsRegistered(ContentUrlHistory entity) { var sqlCommand = $@"SELECT TOP 1 {AllFields} FROM {ContentUrlHistoryTable} - WHERE ContentKey = @contentKey + WHERE ContentKey = @contentKey AND md5_ContentKey = @contentKeyHash ORDER BY CreatedUtc DESC"; var dataTable = _dataExecutor.ExecuteQuery( sqlCommand, - _dataExecutor.CreateStringParameter("contentKey", entity.ContentKey)); + _dataExecutor.CreateStringParameter("contentKey", entity.ContentKey), + _dataExecutor.CreateBinaryParameter("contentKeyHash", CalculateMd5Hash(entity.ContentKey)) + ); var last = ToContentUrlHistory(dataTable).FirstOrDefault(); - return last != null && last.Urls.Count == entity.Urls.Count && last.Urls.All(entity.Urls.Contains); + var result = last != null && last.Urls.Count == entity.Urls.Count && last.Urls.All(entity.Urls.Contains); + + return result; } public IEnumerable<(string contentKey, IReadOnlyCollection histories)> GetAllMoved() { - var sqlCommand = $@"SELECT h.Id, h.ContentKey, h.Urls, h.CreatedUtc + var sqlCommand = $@"SELECT h.Id, h.ContentKey, h.Urls, h.CreatedUtc, h.md5_ContentKey FROM {ContentUrlHistoryTable} h INNER JOIN - (SELECT ContentKey + (SELECT ContentKey, md5_ContentKey FROM {ContentUrlHistoryTable} - GROUP BY ContentKey + GROUP BY ContentKey, md5_ContentKey HAVING COUNT(*) > 1) k - ON h.ContentKey = k.ContentKey + ON h.ContentKey = k.ContentKey AND h.md5_ContentKey = k.md5_ContentKey ORDER BY h.ContentKey, h.CreatedUtc DESC"; var dataTable = _dataExecutor.ExecuteQuery(sqlCommand); @@ -73,18 +88,24 @@ HAVING COUNT(*) > 1) k public IReadOnlyCollection GetMoved(string contentKey) { - var sqlCommand = $@"SELECT h.Id, h.ContentKey, h.Urls, h.CreatedUtc + var contentKeyHash = CalculateMd5Hash(contentKey); + + var sqlCommand = $@"SELECT h.Id, h.ContentKey, h.Urls, h.CreatedUtc, h.md5_ContentKey FROM {ContentUrlHistoryTable} h INNER JOIN (SELECT ContentKey FROM {ContentUrlHistoryTable} + WHERE md5_ContentKey = @contentKeyHash GROUP BY ContentKey HAVING COUNT(*) > 1) k ON h.ContentKey = k.ContentKey - WHERE h.ContentKey = @contentKey + WHERE h.ContentKey = @contentKey AND h.md5_ContentKey = @contentKeyHash ORDER BY h.ContentKey, h.CreatedUtc DESC"; - var dataTable = _dataExecutor.ExecuteQuery(sqlCommand, _dataExecutor.CreateStringParameter("contentKey", contentKey)); + var dataTable = _dataExecutor.ExecuteQuery(sqlCommand, + _dataExecutor.CreateStringParameter("contentKey", contentKey), + _dataExecutor.CreateBinaryParameter("contentKeyHash", contentKeyHash) + ); var histories = ToContentUrlHistory(dataTable); @@ -124,7 +145,8 @@ private void Create(ContentUrlHistory entity) _dataExecutor.CreateGuidParameter("id", entity.Id), _dataExecutor.CreateStringParameter("contentKey", entity.ContentKey), _dataExecutor.CreateStringParameter("urls", ToJson(entity.Urls), -1), - _dataExecutor.CreateDateTimeParameter("createdUtc", entity.CreatedUtc)); + _dataExecutor.CreateDateTimeParameter("createdUtc", entity.CreatedUtc) + ); } private void Update(ContentUrlHistory entity) @@ -145,7 +167,8 @@ private void Update(ContentUrlHistory entity) _dataExecutor.CreateGuidParameter("id", entity.Id), _dataExecutor.CreateStringParameter("contentKey", entity.ContentKey), _dataExecutor.CreateStringParameter("urls", ToJson(entity.Urls), -1), - _dataExecutor.CreateDateTimeParameter("createdUtc", entity.CreatedUtc)); + _dataExecutor.CreateDateTimeParameter("createdUtc", entity.CreatedUtc) + ); } private static string ToJson(ICollection urls) diff --git a/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Configuration/OptimizelyNotFoundHandlerOptions.cs b/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Configuration/OptimizelyNotFoundHandlerOptions.cs index 7241749a..c0c21f4d 100644 --- a/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Configuration/OptimizelyNotFoundHandlerOptions.cs +++ b/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Configuration/OptimizelyNotFoundHandlerOptions.cs @@ -10,7 +10,7 @@ namespace Geta.NotFoundHandler.Optimizely.Infrastructure.Configuration { public class OptimizelyNotFoundHandlerOptions { - public const int CurrentDbVersion = 1; + public const int CurrentDbVersion = 2; public bool AutomaticRedirectsEnabled { get; set; } public RedirectType AutomaticRedirectType { get; set; } = RedirectType.Temporary; diff --git a/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Initialization/Upgrader.cs b/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Initialization/Upgrader.cs index 6c906ca6..15155a1c 100644 --- a/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Initialization/Upgrader.cs +++ b/src/Geta.NotFoundHandler.Optimizely/Infrastructure/Initialization/Upgrader.cs @@ -2,7 +2,6 @@ // Licensed under Apache-2.0. See the LICENSE file in the project root for more information using Geta.NotFoundHandler.Data; -using Geta.NotFoundHandler.Infrastructure.Configuration; using Geta.NotFoundHandler.Optimizely.Infrastructure.Configuration; using Microsoft.Extensions.Logging; @@ -50,9 +49,14 @@ public void Start() /// private void Create() { - var created = CreateContentUrlHistoryTable(); + var result = CreateContentUrlHistoryTable(); - if (created) + if (result) + { + result = CreateContentKeyMd5Column(); + } + + if (result) { CreateVersionNumberSp(); } @@ -76,14 +80,19 @@ CONSTRAINT [PK_ContentUrlHistory] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY] private void Upgrade() { - UpdateVersionNumber(); + var result = CreateContentKeyMd5Column(); + + if (result) + { + UpdateVersionNumber(); + } } private void CreateVersionNumberSp() { _logger.LogInformation("Create Optimizely NotFoundHandler version SP START"); var versionSp = - $@"CREATE PROCEDURE {VersionProcedure} AS RETURN {NotFoundHandlerOptions.CurrentDbVersion}"; + $@"CREATE PROCEDURE {VersionProcedure} AS RETURN {OptimizelyNotFoundHandlerOptions.CurrentDbVersion}"; var created = _dataExecutor.ExecuteNonQuery(versionSp); @@ -105,8 +114,35 @@ private int GetVersionNumber() private void UpdateVersionNumber() { var versionSp = - $@"ALTER PROCEDURE {VersionProcedure} AS RETURN {NotFoundHandlerOptions.CurrentDbVersion}"; + $@"ALTER PROCEDURE {VersionProcedure} AS RETURN {OptimizelyNotFoundHandlerOptions.CurrentDbVersion}"; _dataExecutor.ExecuteNonQuery(versionSp); } + + private bool CreateContentKeyMd5Column() + { + var command = $@" + BEGIN TRY + BEGIN TRANSACTION; + IF NOT EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID('{ContentUrlHistoryTable}') + AND name = 'md5_ContentKey' + ) + BEGIN + ALTER TABLE {ContentUrlHistoryTable} ADD md5_ContentKey AS HASHBYTES('MD5', [ContentKey]); + CREATE INDEX PContentKey_index ON {ContentUrlHistoryTable} (md5_ContentKey); + END + + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + ROLLBACK TRANSACTION; + THROW; + END CATCH; + "; + + return _dataExecutor.ExecuteNonQuery(command); + } } } diff --git a/src/Geta.NotFoundHandler/Data/IDataExecutor.cs b/src/Geta.NotFoundHandler/Data/IDataExecutor.cs index b2bb19d8..7cef4dd2 100644 --- a/src/Geta.NotFoundHandler/Data/IDataExecutor.cs +++ b/src/Geta.NotFoundHandler/Data/IDataExecutor.cs @@ -20,5 +20,6 @@ public interface IDataExecutor DbParameter CreateIntParameter(string name, int value); DbParameter CreateBoolParameter(string name, bool value); DbParameter CreateDateTimeParameter(string name, DateTime value); + DbParameter CreateBinaryParameter(string name, byte[] value, int size = 8000); } } diff --git a/src/Geta.NotFoundHandler/Data/SqlDataExecutor.cs b/src/Geta.NotFoundHandler/Data/SqlDataExecutor.cs index 66a22fed..c2f5be91 100644 --- a/src/Geta.NotFoundHandler/Data/SqlDataExecutor.cs +++ b/src/Geta.NotFoundHandler/Data/SqlDataExecutor.cs @@ -183,6 +183,13 @@ public DbParameter CreateDateTimeParameter(string name, DateTime value) return parameter; } + public DbParameter CreateBinaryParameter(string name, byte[] value, int size = 8000) + { + var parameter = CreateParameter(name, DbType.Binary, size); + parameter.Value = value; + return parameter; + } + private static SqlParameter CreateReturnParameter() { var parameter = new SqlParameter