V13: Align database schemas of migrated and new database (#15934)

* Drop default constraint umbracoCacheInstruction table

* Align umbracoContentVersion table

* Update indexes on external login table

* Align node table

* Make relation type index unique

* Remove user-group default constraint

* Re-order methods

* Make webhook url not nullable

* Cleanup

* Cleanup
This commit is contained in:
Mole
2024-04-02 08:31:38 +02:00
committed by GitHub
parent 43920f9adc
commit d765e69713
2 changed files with 182 additions and 0 deletions

View File

@@ -105,5 +105,6 @@ public class UmbracoPlan : MigrationPlan
To<V_13_0_0.ChangeWebhookRequestObjectColumnToNvarcharMax>("{F74CDA0C-7AAA-48C8-94C6-C6EC3C06F599}");
To<V_13_0_0.ChangeWebhookUrlColumnsToNvarcharMax>("{21C42760-5109-4C03-AB4F-7EA53577D1F5}");
To<V_13_0_0.AddExceptionOccured>("{6158F3A3-4902-4201-835E-1ED7F810B2D8}");
To<V_13_3_0.AlignUpgradedDatabase>("{985AF2BA-69D3-4DBA-95E0-AD3FA7459FA7}");
}
}

View File

@@ -0,0 +1,181 @@
using NPoco;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using ColumnInfo = Umbraco.Cms.Infrastructure.Persistence.SqlSyntax.ColumnInfo;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_3_0;
/// <summary>
/// We see some differences between an updated database and a fresh one,
/// the purpose of this migration is to align the two.
/// </summary>
public class AlignUpgradedDatabase : MigrationBase
{
public AlignUpgradedDatabase(IMigrationContext context)
: base(context)
{
}
protected override void Migrate()
{
// We ignore SQLite since it's considered a development DB
if (DatabaseType == DatabaseType.SQLite)
{
return;
}
ColumnInfo[] columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
Tuple<string, string, string, bool>[] indexes = SqlSyntax.GetDefinedIndexes(Database).ToArray();
DropCacheInstructionDefaultConstraint(columns);
AlignContentVersionTable(columns);
UpdateExternalLoginIndexes(indexes);
AlignNodeTable(columns);
MakeRelationTypeIndexUnique(indexes);
RemoveUserGroupDefault(columns);
MakeWebhookUrlNotNullable(columns);
MakeWebhookLogUrlNotNullable(columns);
}
private void MakeIndexUnique<TDto>(string tableName, string indexName, IEnumerable<Tuple<string, string, string, bool>> indexes)
{
// Let's only mess with the indexes if we have to.
// Indexes are in format TableName, IndexName, ColumnName, IsUnique
Tuple<string, string, string, bool>? loginProviderIndex = indexes.FirstOrDefault(x =>
x.Item1 == tableName && x.Item2 == indexName);
// Item4 == IsUnique
if (loginProviderIndex?.Item4 is false)
{
// The recommended way to change an index from non-unique to unique is to drop and recreate it.
DeleteIndex<TDto>(indexName);
CreateIndex<TDto>(indexName);
}
}
private void RemoveDefaultConstraint(string tableName, string columnName, IEnumerable<ColumnInfo> columns)
{
ColumnInfo? targetColumn = columns
.FirstOrDefault(x => x.TableName == tableName && x.ColumnName == columnName);
if (targetColumn is null)
{
throw new InvalidOperationException($"Could not find {columnName} column on {tableName} table.");
}
if (targetColumn.ColumnDefault is null)
{
return;
}
Delete.DefaultConstraint()
.OnTable(tableName)
.OnColumn(columnName)
.Do();
}
private void RenameColumn(string tableName, string oldColumnName, string newColumnName, IEnumerable<ColumnInfo> columns)
{
ColumnInfo? targetColumn = columns
.FirstOrDefault(x => x.TableName == tableName && x.ColumnName == oldColumnName);
if (targetColumn is null)
{
// The column was not found I.E. the column is correctly named
return;
}
Rename.Column(oldColumnName)
.OnTable(tableName)
.To(newColumnName)
.Do();
}
private void MakeNvarCharColumnNotNullable(string tableName, string columnName, IEnumerable<ColumnInfo> columns)
{
ColumnInfo? targetColumn = columns.FirstOrDefault(x => x.TableName == tableName && x.ColumnName == columnName);
if (targetColumn is null)
{
throw new InvalidOperationException($"Could not find {columnName} column in {tableName} table.");
}
if (targetColumn.IsNullable is false)
{
return;
}
Alter.Table(tableName)
.AlterColumn(columnName)
.AsCustom("nvarchar(max)")
.NotNullable()
.Do();
}
private void DropCacheInstructionDefaultConstraint(IEnumerable<ColumnInfo> columns)
=> RemoveDefaultConstraint("umbracoCacheInstruction", "jsonInstruction", columns);
private void AlignContentVersionTable(ColumnInfo[] columns)
{
// We need to do this to ensure we don't try to rename the constraint if it doesn't exist.
const string tableName = "umbracoContentVersion";
const string columnName = "VersionDate";
ColumnInfo? versionDateColumn = columns
.FirstOrDefault(x => x is { TableName: tableName, ColumnName: columnName });
if (versionDateColumn is null)
{
// The column was not found I.E. the column is correctly named
return;
}
RenameColumn(tableName, columnName, "versionDate", columns);
// Renames the default constraint for the column,
// apparently the content version table used to be prefixed with cms and not umbraco
// We don't have a fluid way to rename the default constraint so we have to use raw SQL
// This should be okay though since we are only running this migration on SQL Server
Sql<ISqlContext> renameConstraintQuery = Database.SqlContext.Sql(
"EXEC sp_rename N'DF_cmsContentVersion_VersionDate', N'DF_umbracoContentVersion_versionDate', N'OBJECT'");
Database.Execute(renameConstraintQuery);
}
private void UpdateExternalLoginIndexes(IEnumerable<Tuple<string, string, string, bool>> indexes)
{
const string userMemberOrKeyIndexName = "IX_umbracoExternalLogin_userOrMemberKey";
MakeIndexUnique<ExternalLoginDto>("umbracoExternalLogin", "IX_umbracoExternalLogin_LoginProvider", indexes);
if (IndexExists(userMemberOrKeyIndexName))
{
return;
}
CreateIndex<ExternalLoginDto>(userMemberOrKeyIndexName);
}
private void AlignNodeTable(ColumnInfo[] columns)
{
const string tableName = "umbracoNode";
RenameColumn(tableName, "parentID", "parentId", columns);
RenameColumn(tableName, "uniqueID", "uniqueId", columns);
const string extraIndexName = "IX_umbracoNode_ParentId";
if (IndexExists(extraIndexName))
{
DeleteIndex<NodeDto>(extraIndexName);
}
}
private void MakeRelationTypeIndexUnique(Tuple<string, string, string, bool>[] indexes)
=> MakeIndexUnique<RelationTypeDto>("umbracoRelationType", "IX_umbracoRelationType_alias", indexes);
private void RemoveUserGroupDefault(ColumnInfo[] columns)
=> RemoveDefaultConstraint("umbracoUserGroup", "hasAccessToAllLanguages", columns);
private void MakeWebhookUrlNotNullable(ColumnInfo[] columns)
=> MakeNvarCharColumnNotNullable("umbracoWebhook", "url", columns);
private void MakeWebhookLogUrlNotNullable(ColumnInfo[] columns)
=> MakeNvarCharColumnNotNullable("umbracoWebhookLog", "url", columns);
}