Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AsyncMigrationBase, update base classes and call async methods #17057

Open
wants to merge 1 commit into
base: v15/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,17 @@ public PackageMigrationRunner(
/// </summary>
/// <param name="packageName"></param>
/// <returns></returns>
public IEnumerable<ExecutedMigrationPlan> RunPackageMigrationsIfPending(string packageName)
public async Task<IEnumerable<ExecutedMigrationPlan>> RunPackageMigrationsIfPendingAsync(string packageName)
{
IReadOnlyDictionary<string, string?>? keyValues =
_keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
IReadOnlyDictionary<string, string?>? keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
IReadOnlyList<string> pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues);

IEnumerable<string> packagePlans = _packageMigrationPlans.Values
.Where(x => x.PackageName.InvariantEquals(packageName))
.Where(x => pendingMigrations.Contains(x.Name))
.Select(x => x.Name);

return RunPackagePlans(packagePlans);
return await RunPackagePlansAsync(packagePlans).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -81,7 +80,7 @@ public async Task<Attempt<bool, PackageMigrationOperationStatus>> RunPendingPack
}

// Run the migrations
IEnumerable<ExecutedMigrationPlan> executedMigrationPlans = RunPackageMigrationsIfPending(packageName);
IEnumerable<ExecutedMigrationPlan> executedMigrationPlans = await RunPackageMigrationsIfPendingAsync(packageName).ConfigureAwait(false);

if (executedMigrationPlans.Any(plan => plan.Successful == false))
{
Expand All @@ -98,7 +97,7 @@ public async Task<Attempt<bool, PackageMigrationOperationStatus>> RunPendingPack
/// <param name="plansToRun"></param>
/// <returns></returns>
/// <exception cref="Exception">If any plan fails it will throw an exception.</exception>
public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> plansToRun)
public async Task<IEnumerable<ExecutedMigrationPlan>> RunPackagePlansAsync(IEnumerable<string> plansToRun)
{
List<ExecutedMigrationPlan> results = new();

Expand All @@ -120,7 +119,7 @@ public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> pl
Upgrader upgrader = new(plan);

// This may throw, if so the transaction will be rolled back
results.Add(upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService));
results.Add(await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService).ConfigureAwait(false));
}
}

Expand Down
22 changes: 8 additions & 14 deletions src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,42 +36,36 @@ public PremigrationUpgrader(
_keyValueService = keyValueService;
}

public Task HandleAsync(RuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken)
public async Task HandleAsync(RuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken)
{
// no connection string set
if (_umbracoDatabaseFactory.Configured is false)
{
return Task.CompletedTask;
return;
}

if (_databaseBuilder.IsUmbracoInstalled() is false)
{
return Task.CompletedTask;
return;
}

var plan = new UmbracoPremigrationPlan();
if (HasMissingPremigrations(plan) is false)
{
return Task.CompletedTask;
return;
}

using (_profilingLogger.IsEnabled(LogLevel.Verbose) is false ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>(
"Starting premigration upgrade.",
"Unattended premigration completed."))
using (_profilingLogger.IsEnabled(LogLevel.Verbose) is false ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>("Starting premigration upgrade.", "Unattended premigration completed."))
{
DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan);
DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false);
if (result?.Success is false)
{
var innerException = new IOException(
"An error occurred while running the premigration upgrade.\n" + result.Message);
var innerException = new IOException("An error occurred while running the premigration upgrade.\n" + result.Message);
_runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException);
}

notification.UpgradeResult =
RuntimePremigrationsUpgradeNotification.PremigrationUpgradeResult.CoreUpgradeComplete;
notification.UpgradeResult = RuntimePremigrationsUpgradeNotification.PremigrationUpgradeResult.CoreUpgradeComplete;
}

return Task.CompletedTask;
}

private bool HasMissingPremigrations(UmbracoPremigrationPlan umbracoPremigrationPlan)
Expand Down
41 changes: 12 additions & 29 deletions src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public UnattendedUpgrader(
_packageMigrationRunner = packageMigrationRunner;
}

public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
{
if (_runtimeState.RunUnattendedBootLogic())
{
Expand All @@ -47,53 +47,41 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance
case RuntimeLevelReason.UpgradeMigrations:
{
var plan = new UmbracoPlan(_umbracoVersion);
using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Verbose) ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>(
"Starting unattended upgrade.",
"Unattended upgrade completed."))
using (!_profilingLogger.IsEnabled(LogLevel.Verbose) ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>("Starting unattended upgrade.", "Unattended upgrade completed."))
{
DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan);
DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false);
if (result?.Success == false)
{
var innerException = new UnattendedInstallException(
"An error occurred while running the unattended upgrade.\n" + result.Message);
var innerException = new UnattendedInstallException($"An error occurred while running the unattended upgrade.\n{result.Message}");
_runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException);
}

notification.UnattendedUpgradeResult =
RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete;
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete;
}
}

break;
case RuntimeLevelReason.UpgradePackageMigrations:
{
if (!_runtimeState.StartupState.TryGetValue(
RuntimeState.PendingPackageMigrationsStateKey,
out var pm)
|| pm is not IReadOnlyList<string> pendingMigrations)
if (!_runtimeState.StartupState.TryGetValue(RuntimeState.PendingPackageMigrationsStateKey, out var pm) || pm is not IReadOnlyList<string> pendingMigrations)
{
throw new InvalidOperationException(
$"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state");
throw new InvalidOperationException($"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state");
}

if (pendingMigrations.Count == 0)
{
throw new InvalidOperationException(
"No pending migrations found but the runtime level reason is " +
RuntimeLevelReason.UpgradePackageMigrations);
throw new InvalidOperationException($"No pending migrations found but the runtime level reason is {RuntimeLevelReason.UpgradePackageMigrations}");
}

try
{
_packageMigrationRunner.RunPackagePlans(pendingMigrations);
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult
.PackageMigrationComplete;
await _packageMigrationRunner.RunPackagePlansAsync(pendingMigrations).ConfigureAwait(false);
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete;
}
catch (Exception ex)
{
SetRuntimeError(ex);
notification.UnattendedUpgradeResult =
RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors;
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors;
}
}

Expand All @@ -102,13 +90,8 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance
throw new InvalidOperationException("Invalid reason " + _runtimeState.Reason);
}
}

return Task.CompletedTask;
}

private void SetRuntimeError(Exception exception)
=> _runtimeState.Configure(
RuntimeLevel.BootFailed,
RuntimeLevelReason.BootFailedOnException,
exception);
=> _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, exception);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Installer;
Expand Down Expand Up @@ -35,21 +35,21 @@ public DatabaseUpgradeStep(

public Task<Attempt<InstallationResult>> ExecuteAsync() => ExecuteInternalAsync();

private Task<Attempt<InstallationResult>> ExecuteInternalAsync()
private async Task<Attempt<InstallationResult>> ExecuteInternalAsync()
{
_logger.LogInformation("Running 'Upgrade' service");

var plan = new UmbracoPlan(_umbracoVersion);
// TODO: Clear CSRF cookies with notification.

DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan);
DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false);

if (result?.Success == false)
{
return Task.FromResult(FailWithMessage("The database failed to upgrade. ERROR: " + result.Message));
return FailWithMessage("The database failed to upgrade. ERROR: " + result.Message);
}

return Task.FromResult(Success());
return Success();
}

public Task<bool> RequiresExecutionAsync(InstallData model) => ShouldExecute();
Expand Down
147 changes: 147 additions & 0 deletions src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions;

Check warning on line 1 in src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: Primitive Obsession

In this module, 80.0% of all function arguments are primitive types, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.

Check warning on line 1 in src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: String Heavy Function Arguments

In this module, 80.0% of all arguments to its 17 functions are strings. The threshold for string arguments is 39.0%. The functions in this file have a high ratio of strings as arguments. Avoid adding more.
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
using Umbraco.Extensions;

namespace Umbraco.Cms.Infrastructure.Migrations;

/// <summary>
/// Provides a base class to all migrations.
/// </summary>
public abstract partial class AsyncMigrationBase
{
// provides extra methods for migrations
protected void AddColumn<T>(string columnName)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
AddColumn(table, table.Name!, columnName);
}

protected void AddColumnIfNotExists<T>(IEnumerable<ColumnInfo> columns, string columnName)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
if (columns.Any(x => x.TableName.InvariantEquals(table.Name) && !x.ColumnName.InvariantEquals(columnName)))
{
AddColumn(table, table.Name!, columnName);
}
}

protected void AddColumn<T>(string tableName, string columnName)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
AddColumn(table, tableName, columnName);
}

protected void AddColumnIfNotExists<T>(IEnumerable<ColumnInfo> columns, string tableName, string columnName)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
if (columns.Any(x => x.TableName.InvariantEquals(tableName) && !x.ColumnName.InvariantEquals(columnName)))
{
AddColumn(table, tableName, columnName);
}
}

protected void AddColumn<T>(string columnName, out IEnumerable<string> sqls)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
AddColumn(table, table.Name!, columnName, out sqls);
}

private void AddColumn(TableDefinition table, string tableName, string columnName)
{
if (ColumnExists(tableName, columnName))
{
return;
}

ColumnDefinition? column = table.Columns.First(x => x.Name == columnName);
var createSql = SqlSyntax.Format(column);

Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do();
}

Check warning on line 61 in src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: Code Duplication

The module contains 2 functions with similar structure: AddColumn,AddColumn. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

protected void AddColumn<T>(string tableName, string columnName, out IEnumerable<string> sqls)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
AddColumn(table, tableName, columnName, out sqls);
}

protected void AlterColumn<T>(string tableName, string columnName)
{
TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
ColumnDefinition? column = table.Columns.First(x => x.Name == columnName);
SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out IEnumerable<string>? sqls);
foreach (var sql in sqls)
{
Execute.Sql(sql).Do();
}
}

private void AddColumn(TableDefinition table, string tableName, string columnName, out IEnumerable<string> sqls)
{
if (ColumnExists(tableName, columnName))
{
sqls = Enumerable.Empty<string>();
return;
}

ColumnDefinition? column = table.Columns.First(x => x.Name == columnName);
var createSql = SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out sqls);
Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do();
}

protected void ReplaceColumn<T>(string tableName, string currentName, string newName)
{
Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do();
AlterColumn<T>(tableName, newName);
}

protected bool TableExists(string tableName)
{
IEnumerable<string>? tables = SqlSyntax.GetTablesInSchema(Context.Database);
return tables.Any(x => x.InvariantEquals(tableName));
}

protected bool IndexExists(string indexName)
{
IEnumerable<Tuple<string, string, string, bool>>? indexes = SqlSyntax.GetDefinedIndexes(Context.Database);
return indexes.Any(x => x.Item2.InvariantEquals(indexName));
}

protected void CreateIndex<T>(string toCreate)
{
TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax);
IndexDefinition index = tableDef.Indexes.First(x => x.Name == toCreate);
new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) }
.Execute();
}

protected void DeleteIndex<T>(string toDelete)
{
if (!IndexExists(toDelete))
{
return;
}

TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax);
Delete.Index(toDelete).OnTable(tableDef.Name).Do();
}

protected bool PrimaryKeyExists(string tableName, string primaryKeyName)
{
return SqlSyntax.DoesPrimaryKeyExist(Context.Database, tableName, primaryKeyName);
}

protected bool ColumnExists(string tableName, string columnName)
{
ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray();
return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName));
}

protected string? ColumnType(string tableName, string columnName)
{
ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray();
ColumnInfo? column = columns.FirstOrDefault(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName));
return column?.DataType;
}
}
Loading
Loading