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:
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user