diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 8032cd42a6..1371486ce5 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -2,6 +2,7 @@ using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -72,6 +73,9 @@ namespace Umbraco.Core.Migrations.Install if (tableName.Equals(Constants.DatabaseSchema.Tables.TaskType)) CreateTaskTypeData(); + if (tableName.Equals(Constants.DatabaseSchema.Tables.KeyValue)) + CreateKeyValueData(); + _logger.Info($"Done creating table {tableName} data."); } @@ -278,5 +282,16 @@ namespace Umbraco.Core.Migrations.Install { _database.Insert(Constants.DatabaseSchema.Tables.TaskType, "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); } + + private void CreateKeyValueData() + { + // on install, initialize the umbraco migration plan with the final state + + var plan = new UmbracoPlan(); + var stateValueKey = Upgrader.GetStateValueKey(plan); + var finalState = plan.FinalState; + + _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, Updated = DateTime.Now }); + } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index 759e060a1f..dfd173681b 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -399,13 +399,13 @@ namespace Umbraco.Core.Migrations.Install { //Execute the Create Table sql var created = _database.Execute(new Sql(createSql)); - _logger.Info($"Create Table sql {created}:\n {createSql}"); + _logger.Info($"Create Table '{tableName}' ({created}):\n {createSql}"); //If any statements exists for the primary key execute them here if (string.IsNullOrEmpty(createPrimaryKeySql) == false) { var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); - _logger.Info($"Primary Key sql {createdPk}:\n {createPrimaryKeySql}"); + _logger.Info($"Create Primary Key ({createdPk}):\n {createPrimaryKeySql}"); } //Turn on identity insert if db provider is not mysql @@ -431,21 +431,21 @@ namespace Umbraco.Core.Migrations.Install foreach (var sql in indexSql) { var createdIndex = _database.Execute(new Sql(sql)); - _logger.Info($"Create Index sql {createdIndex}:\n {sql}"); + _logger.Info($"Create Index ({createdIndex}):\n {sql}"); } //Loop through foreignkey statements and execute sql foreach (var sql in foreignSql) { var createdFk = _database.Execute(new Sql(sql)); - _logger.Info($"Create Foreign Key sql {createdFk}:\n {sql}"); + _logger.Info($"Create Foreign Key ({createdFk}):\n {sql}"); } transaction.Complete(); } } - _logger.Info($"New table '{tableName}' was created"); + _logger.Info($"Created table '{tableName}'"); } public void DropTable(string tableName) diff --git a/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs b/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs index 3212e81603..7481444bd8 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Migrations.Upgrade public string Name => Plan.Name; - public string StateValueKey => "Umbraco.Core.Upgrader.State+" + Plan.Name; + public string StateValueKey => GetStateValueKey(Plan); protected IScopeProvider ScopeProvider { get; } @@ -71,5 +71,7 @@ namespace Umbraco.Core.Migrations.Upgrade scope.Complete(); } } + + public static string GetStateValueKey(MigrationPlan plan) => "Umbraco.Core.Upgrader.State+" + plan.Name; } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 9ba8e8d4a3..41e2c771b7 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -330,17 +330,19 @@ namespace Umbraco.Core.Runtime { // no scope, no key value service - just directly accessing the database + var umbracoPlan = new UmbracoPlan(); + var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan); + string state; using (var database = databaseFactory.CreateDatabase()) { var sql = databaseFactory.SqlContext.Sql() .Select() .From() - .Where(x => x.Key == "Umbraco.Core.Upgrader.State+Umbraco.Core"); + .Where(x => x.Key == stateValueKey); state = database.FirstOrDefault(sql)?.Value; } - var umbracoPlan = new UmbracoPlan(); var finalState = umbracoPlan.FinalState; logger.Debug($"Final upgrade state is \"{finalState}\", database contains \"{state ?? ""}\"."); diff --git a/src/Umbraco.Core/Services/IKeyValueService.cs b/src/Umbraco.Core/Services/IKeyValueService.cs index 35faf19160..192c99bee7 100644 --- a/src/Umbraco.Core/Services/IKeyValueService.cs +++ b/src/Umbraco.Core/Services/IKeyValueService.cs @@ -1,9 +1,35 @@ namespace Umbraco.Core.Services { + /// + /// Manages the simplified key/value store. + /// public interface IKeyValueService { + /// + /// Gets a value. + /// + /// Returns null if no value was found for the key. string GetValue(string key); + + /// + /// Sets a value. + /// void SetValue(string key, string value); + + /// + /// Sets a value. + /// + /// Sets the value to if the value is , + /// and returns true; otherwise throws an exception. In other words, ensures that the value has not changed + /// before setting it. void SetValue(string key, string originValue, string newValue); + + /// + /// Tries to set a value. + /// + /// Sets the value to if the value is , + /// and returns true; otherwise returns false. In other words, ensures that the value has not changed + /// before setting it. + bool TrySetValue(string key, string originValue, string newValue); } } diff --git a/src/Umbraco.Core/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/Implement/KeyValueService.cs index 3a3a67d2bf..6f911ceedc 100644 --- a/src/Umbraco.Core/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/Implement/KeyValueService.cs @@ -39,23 +39,28 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - // assume that if the lock object exists, then everything is ok + // assume that if the lock object for key/value exists, then everything is ok if (scope.Database.Exists(Constants.Locks.KeyValues)) { scope.Complete(); return; } - // drop the 'identity' on primary key + // drop the 'identity' on umbracoLock primary key foreach (var sql in new[] { + // create a temp. id column and copy values "alter table umbracoLock add column nid int null;", "update umbracoLock set nid = id;", + // drop the id column entirely (cannot just drop identity) "alter table umbracoLock drop constraint PK_umbracoLock;", "alter table umbracoLock drop column id;", + // recreate the id column without identity and copy values "alter table umbracoLock add column id int null;", "update umbracoLock set id = nid;", + // drop the temp. id column "alter table umbracoLock drop column nid;", + // complete the primary key "alter table umbracoLock alter column id int not null;", "alter table umbracoLock add constraint PK_umbracoLock primary key (id);" }) @@ -73,6 +78,7 @@ VALUES ({Constants.Locks.KeyValues}, 'KeyValues', 1);"); } } + /// public string GetValue(string key) { EnsureInitialized(); @@ -86,6 +92,7 @@ VALUES ({Constants.Locks.KeyValues}, 'KeyValues', 1);"); } } + /// public void SetValue(string key, string value) { EnsureInitialized(); @@ -119,7 +126,15 @@ VALUES ({Constants.Locks.KeyValues}, 'KeyValues', 1);"); } } + /// public void SetValue(string key, string originValue, string newValue) + { + if (!TrySetValue(key, originValue, newValue)) + throw new InvalidOperationException("Could not set the value."); + } + + /// + public bool TrySetValue(string key, string originValue, string newValue) { EnsureInitialized(); @@ -130,11 +145,8 @@ VALUES ({Constants.Locks.KeyValues}, 'KeyValues', 1);"); var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); var dto = scope.Database.Fetch(sql).FirstOrDefault(); - if (dto == null) - throw new InvalidOperationException("Key not found."); - - if (dto.Value != originValue) - throw new InvalidOperationException("Value has changed."); + if (dto == null || dto.Value != originValue) + return false; dto.Value = newValue; dto.Updated = DateTime.Now; @@ -142,6 +154,8 @@ VALUES ({Constants.Locks.KeyValues}, 'KeyValues', 1);"); scope.Complete(); } + + return true; } } }