From 869e0dcbd22401ae8a1bcd8942798b66ecf363be Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 Jun 2015 18:10:50 +0200 Subject: [PATCH] Updates the upgrader to ensure that the latest migrations are run if they don't exist, fixes the migration runner null check, adds test to subtract a revision number from a Version object. --- src/Umbraco.Core/DatabaseContext.cs | 29 ++++++-- .../Factories/MigrationEntryFactory.cs | 8 ++- .../Initial/DatabaseSchemaResult.cs | 16 ++++- .../Persistence/Migrations/MigrationRunner.cs | 5 +- .../AddUserColumns.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Core/VersionExtensions.cs | 66 +++++++++++++++++++ src/Umbraco.Core/XmlHelper.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Tests/VersionExtensionTests.cs | 30 +++++++++ 10 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Core/VersionExtensions.cs create mode 100644 src/Umbraco.Tests/VersionExtensionTests.cs diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index df5ed33bd3..4186eb0d73 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -579,10 +579,11 @@ namespace Umbraco.Core message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); - var installedVersion = schemaResult.DetermineInstalledVersion(); + + var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedVersion.Equals(new Version(0, 0, 0))) + if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) { var helper = new DatabaseSchemaHelper(database, _logger, SqlSyntax); helper.CreateDatabaseSchema(true, applicationContext); @@ -634,14 +635,30 @@ namespace Umbraco.Core var message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); - var installedVersion = schemaResult.DetermineInstalledVersion(); + + var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); + var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); + + var targetVersion = UmbracoVersion.Current; + //In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will + // return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the + // migrations are run for the target version + if (installedMigrationVersion == new Version(0, 0, 0) && installedSchemaVersion > new Version(0, 0, 0)) + { + //set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute + installedMigrationVersion = targetVersion.SubtractRevision(); + } + //DO the upgrade! var currentVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) - ? installedVersion - : new Version(GlobalSettings.ConfigurationStatus); - var targetVersion = UmbracoVersion.Current; + //Take the minimum version between the detected schema version and the installed migration version + ? new[] {installedSchemaVersion, installedMigrationVersion}.Min() + //Take the minimum version between the installed migration version and the version specified in the config + : new[] { new Version(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min(); + + var runner = new MigrationRunner(migrationEntryService, _logger, currentVersion, targetVersion, GlobalSettings.UmbracoMigrationName); var upgraded = runner.Execute(database, true); message = message + "

Upgrade completed!

"; diff --git a/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs index 01feb74eaa..712acdfabe 100644 --- a/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs @@ -8,7 +8,13 @@ namespace Umbraco.Core.Persistence.Factories { public MigrationEntry BuildEntity(MigrationDto dto) { - var model = new MigrationEntry(dto.Id, dto.CreateDate, dto.Name, Version.Parse(dto.Version)); + Version parsed; + if (Version.TryParse(dto.Version, out parsed) == false) + { + throw new FormatException("Cannot parse the version string in the database to a real Version object: " + dto.Version); + } + + var model = new MigrationEntry(dto.Id, dto.CreateDate, dto.Name, parsed); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 model.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index c2acbd3c97..bd99799e11 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -5,6 +5,7 @@ using System.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Migrations.Initial { @@ -35,7 +36,20 @@ namespace Umbraco.Core.Persistence.Migrations.Initial internal IEnumerable DbIndexDefinitions { get; set; } /// - /// Determines the version of the currently installed database. + /// Checks in the db which version is installed based on the migrations that have been run + /// + /// + /// + public Version DetermineInstalledVersionByMigrations(IMigrationEntryService migrationEntryService) + { + var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); + var mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + + return mostrecent ?? new Version(0, 0, 0); + } + + /// + /// Determines the version of the currently installed database by detecting the current database structure /// /// /// A with Major and Minor values for diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 8d19cff387..412d97617c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Migrations [Obsolete("Use the ctor that specifies all dependencies instead")] public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName) - : this(logger, currentVersion, targetVersion, productName, Enumerable.Empty().ToArray()) + : this(logger, currentVersion, targetVersion, productName, null) { } @@ -53,7 +53,8 @@ namespace Umbraco.Core.Persistence.Migrations _currentVersion = currentVersion; _targetVersion = targetVersion; _productName = productName; - _migrations = migrations; + //ensure this is null if there aren't any + _migrations = migrations.Length == 0 ? null : migrations; } /// diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs index 9e0bb26ab9..46fde95005 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastPasswordChangeDate")) == false) Create.Column("lastPasswordChangeDate").OnTable("umbracoUser").AsDateTime().Nullable(); - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastLoginDate")) == false); + if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastLoginDate")) == false) Create.Column("lastLoginDate").OnTable("umbracoUser").AsDateTime().Nullable(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2259044292..c3cc462faf 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1313,6 +1313,7 @@ + diff --git a/src/Umbraco.Core/VersionExtensions.cs b/src/Umbraco.Core/VersionExtensions.cs new file mode 100644 index 0000000000..9d60c308cf --- /dev/null +++ b/src/Umbraco.Core/VersionExtensions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core +{ + internal static class VersionExtensions + { + public static Version SubtractRevision(this Version version) + { + var parts = new List(new[] {version.Major, version.Minor, version.Build, version.Revision}); + + //remove all prefixed zero parts + while (parts[0] <= 0) + { + parts.RemoveAt(0); + if (parts.Count == 0) break; + } + + for (int index = 0; index < parts.Count; index++) + { + var part = parts[index]; + if (part <= 0) + { + parts.RemoveAt(index); + index++; + } + else + { + //break when there isn't a zero part + break; + } + } + + if (parts.Count == 0) throw new InvalidOperationException("Cannot subtract a revision from a zero version"); + + var lastNonZero = parts.FindLastIndex(i => i > 0); + + //subtract 1 from the last non-zero + parts[lastNonZero] = parts[lastNonZero] - 1; + + //the last non zero is actually the revision so we can just return + if (lastNonZero == (parts.Count -1)) + { + return FromList(parts); + } + + //the last non zero isn't the revision so the remaining zero's need to be replaced with int.max + for (var i = lastNonZero + 1; i < parts.Count; i++) + { + parts[i] = int.MaxValue; + } + + return FromList(parts); + } + + private static Version FromList(IList parts) + { + while (parts.Count < 4) + { + parts.Insert(0, 0); + } + return new Version(parts[0], parts[1], parts[2], parts[3]); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/XmlHelper.cs b/src/Umbraco.Core/XmlHelper.cs index 5fcca2863e..1978c329ab 100644 --- a/src/Umbraco.Core/XmlHelper.cs +++ b/src/Umbraco.Core/XmlHelper.cs @@ -12,7 +12,7 @@ using Umbraco.Core.IO; namespace Umbraco.Core { - /// + /// /// The XmlHelper class contains general helper methods for working with xml in umbraco. /// public class XmlHelper diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1ab01e3d24..b8954e98f0 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -551,6 +551,7 @@ + diff --git a/src/Umbraco.Tests/VersionExtensionTests.cs b/src/Umbraco.Tests/VersionExtensionTests.cs new file mode 100644 index 0000000000..ca8fea0393 --- /dev/null +++ b/src/Umbraco.Tests/VersionExtensionTests.cs @@ -0,0 +1,30 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests +{ + [TestFixture] + public class VersionExtensionTests + { + [TestCase(1, 0, 0, 0, "0.2147483647.2147483647.2147483647")] + [TestCase(1, 1, 0, 0, "1.0.2147483647.2147483647")] + [TestCase(1, 1, 1, 0, "1.1.0.2147483647")] + [TestCase(1, 1, 1, 1, "1.1.1.0")] + [TestCase(0, 1, 0, 0, "0.0.2147483647.2147483647")] + [TestCase(0, 1, 1, 0, "0.1.0.2147483647")] + [TestCase(0, 1, 1, 1, "0.1.1.0")] + [TestCase(0, 0, 1, 0, "0.0.0.2147483647")] + [TestCase(0, 0, 1, 1, "0.0.1.0")] + [TestCase(0, 0, 0, 1, "0.0.0.0")] + [TestCase(7, 3, 0, 0, "7.2.2147483647.2147483647")] + public void Subract_Revision(int major, int minor, int build, int rev, string outcome) + { + var version = new Version(major, minor, build, rev); + + var result = version.SubtractRevision(); + + Assert.AreEqual(new Version(outcome), result); + } + } +} \ No newline at end of file