From bbe6194b06166584db00def4a4e5ec1fcc4f4afb Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 14 Mar 2014 13:04:20 +1100 Subject: [PATCH] Exception is thrown if trying to run mysql in med trust. Migrations arranged to execute schema migrations first and then data migrations. If a schema migration fails it will attempt to run the downgrade scripts of the migrations if running mysql since schema changes aren't supported in transactions. Updated all sql syntax providers to try to return only the column indexes, not key indexes. Updates the db version checker to include errors for all missing columns, indexes and constraints in the db and vice versa in the schema. --- src/Umbraco.Core/DatabaseContext.cs | 41 ++++-- .../CatastrophicDataLossException.cs | 22 +++ .../Initial/DatabaseSchemaCreation.cs | 136 +++++++++++++----- .../Initial/DatabaseSchemaResult.cs | 18 ++- .../Migrations/MigrationAttribute.cs | 2 +- .../Persistence/Migrations/MigrationRunner.cs | 66 +++++++-- .../Persistence/Migrations/SchemaMigration.cs | 11 ++ .../RemoveUmbracoAppConstraints.cs | 2 +- .../TargetVersionSix/DeleteAppTables.cs | 3 + .../NewCmsContentType2ContentTypeTable.cs | 2 +- .../RemoveMasterContentTypeColumn.cs | 2 +- .../TargetVersionSix/RenameCmsTabTable.cs | 2 +- .../TargetVersionSix/RenameTabIdColumn.cs | 2 +- ...teCmsContentTypeAllowedContentTypeTable.cs | 2 +- .../UpdateCmsContentTypeTable.cs | 2 +- .../UpdateCmsContentVersionTable.cs | 2 +- .../UpdateCmsPropertyTypeGroupTable.cs | 2 +- .../CreateServerRegistryTable.cs | 6 +- .../AdditionalIndexesAndKeys.cs | 22 +-- .../ChangePasswordColumn.cs | 21 +++ .../UpdateToNewMemberPropertyAliases.cs | 4 +- .../SqlSyntax/MySqlSyntaxProvider.cs | 7 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 4 +- .../SqlSyntax/SqlServerSyntaxProvider.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 3 + .../Migrations/MigrationRunnerTests.cs | 6 +- src/Umbraco.Web.UI/install/Default.aspx.cs | 5 +- 27 files changed, 292 insertions(+), 104 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/CatastrophicDataLossException.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/SchemaMigration.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index e074297eb8..2bc46688f2 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -3,6 +3,7 @@ using System.Configuration; using System.Data.SqlServerCe; using System.IO; using System.Linq; +using System.Web; using System.Web.Configuration; using System.Xml.Linq; using Umbraco.Core.Configuration; @@ -426,6 +427,13 @@ namespace Umbraco.Core if (_result == null) { + + if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted + && ProviderName == "MySql.Data.MySqlClient") + { + throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); + } + var database = new UmbracoDatabase(_connectionString, ProviderName); var dbSchema = new DatabaseSchemaCreation(database); _result = dbSchema.ValidateSchema(); @@ -434,15 +442,15 @@ namespace Umbraco.Core } internal Result CreateDatabaseSchemaAndData() - { - var readyForInstall = CheckReadyForInstall(); - if (readyForInstall.Success == false) - { - return readyForInstall.Result; - } - + { try { + var readyForInstall = CheckReadyForInstall(); + if (readyForInstall.Success == false) + { + return readyForInstall.Result; + } + LogHelper.Info("Database configuration status: Started"); string message; @@ -500,14 +508,15 @@ namespace Umbraco.Core /// internal Result UpgradeSchemaAndData() { - var readyForInstall = CheckReadyForInstall(); - if (readyForInstall.Success == false) - { - return readyForInstall.Result; - } - try { + + var readyForInstall = CheckReadyForInstall(); + if (readyForInstall.Success == false) + { + return readyForInstall.Result; + } + LogHelper.Info("Database upgrade started"); var database = new UmbracoDatabase(_connectionString, ProviderName); @@ -567,6 +576,12 @@ namespace Umbraco.Core private Attempt CheckReadyForInstall() { + if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted + && ProviderName == "MySql.Data.MySqlClient") + { + throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); + } + if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) { return Attempt.Fail(new Result diff --git a/src/Umbraco.Core/Persistence/Migrations/CatastrophicDataLossException.cs b/src/Umbraco.Core/Persistence/Migrations/CatastrophicDataLossException.cs new file mode 100644 index 0000000000..9c425a9e82 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/CatastrophicDataLossException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Used if a migration has executed but the whole process has failed and cannot be rolled back + /// + internal class CatastrophicDataLossException : Exception + { + public CatastrophicDataLossException(string msg) + : base(msg) + { + + } + + public CatastrophicDataLossException(string msg, Exception inner) + : base(msg, inner) + { + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 27acb334c4..0220e146b0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -141,50 +141,31 @@ namespace Umbraco.Core.Persistence.Migrations.Initial result.TableDefinitions.Add(tableDefinition); } - //Check tables in configured database against tables in schema - var tablesInDatabase = SqlSyntaxContext.SqlSyntaxProvider.GetTablesInSchema(_database).ToList(); - var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); - //Add valid and invalid table differences to the result object - var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var tableName in validTableDifferences) - { - result.ValidTables.Add(tableName); - } - var invalidTableDifferences = - tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var tableName in invalidTableDifferences) - { - result.Errors.Add(new Tuple("Table", tableName)); - } + ValidateDbTables(result); - //Check columns in configured database against columns in schema - var columnsInDatabase = SqlSyntaxContext.SqlSyntaxProvider.GetColumnsInSchema(_database); - var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); - var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); - //Add valid and invalid column differences to the result object - var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var column in validColumnDifferences) - { - result.ValidColumns.Add(column); - } - var invalidColumnDifferences = columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var column in invalidColumnDifferences) - { - result.Errors.Add(new Tuple("Column", column)); - } + ValidateDbColumns(result); + ValidateDbIndexes(result); + + ValidateDbConstraints(result); + + return result; + } + + private void ValidateDbConstraints(DatabaseSchemaResult result) + { //MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks. //TODO: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers. // ALso note that to get the constraints for MySql we have to open a connection which we currently have not. if (SqlSyntaxContext.SqlSyntaxProvider is MySqlSyntaxProvider) - return result; + return; //Check constraints in configured database against constraints in schema var constraintsInDatabase = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList(); var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList(); var indexesInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("IX_")).Select(x => x.Item3).ToList(); + var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); var unknownConstraintsInDatabase = constraintsInDatabase.Where( x => @@ -192,7 +173,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)).ToList(); - var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); + //Add valid and invalid foreign key differences to the result object foreach (var unknown in unknownConstraintsInDatabase) { @@ -205,40 +186,121 @@ namespace Umbraco.Core.Persistence.Migrations.Initial result.Errors.Add(new Tuple("Unknown", unknown)); } } + + //Foreign keys: + var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var foreignKey in validForeignKeyDifferences) { result.ValidConstraints.Add(foreignKey); } - var invalidForeignKeyDifferences = foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + var invalidForeignKeyDifferences = + foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); foreach (var foreignKey in invalidForeignKeyDifferences) { result.Errors.Add(new Tuple("Constraint", foreignKey)); } + + + //Primary keys: + //Add valid and invalid primary key differences to the result object var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var primaryKey in validPrimaryKeyDifferences) { result.ValidConstraints.Add(primaryKey); } - var invalidPrimaryKeyDifferences = primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + var invalidPrimaryKeyDifferences = + primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); foreach (var primaryKey in invalidPrimaryKeyDifferences) { result.Errors.Add(new Tuple("Constraint", primaryKey)); } + + //Constaints: + + //NOTE: SD: The colIndex checks above should really take care of this but I need to keep this here because it was here before + // and some schema validation checks might rely on this data remaining here! //Add valid and invalid index differences to the result object var validIndexDifferences = indexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); foreach (var index in validIndexDifferences) { result.ValidConstraints.Add(index); } - var invalidIndexDifferences = indexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); + var invalidIndexDifferences = + indexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(indexesInSchema.Except(indexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); foreach (var index in invalidIndexDifferences) { result.Errors.Add(new Tuple("Constraint", index)); } + } - return result; + private void ValidateDbColumns(DatabaseSchemaResult result) + { + //Check columns in configured database against columns in schema + var columnsInDatabase = SqlSyntaxContext.SqlSyntaxProvider.GetColumnsInSchema(_database); + var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); + var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); + //Add valid and invalid column differences to the result object + var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var column in validColumnDifferences) + { + result.ValidColumns.Add(column); + } + + var invalidColumnDifferences = + columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var column in invalidColumnDifferences) + { + result.Errors.Add(new Tuple("Column", column)); + } + } + + private void ValidateDbTables(DatabaseSchemaResult result) + { + //Check tables in configured database against tables in schema + var tablesInDatabase = SqlSyntaxContext.SqlSyntaxProvider.GetTablesInSchema(_database).ToList(); + var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); + //Add valid and invalid table differences to the result object + var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var tableName in validTableDifferences) + { + result.ValidTables.Add(tableName); + } + + var invalidTableDifferences = + tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var tableName in invalidTableDifferences) + { + result.Errors.Add(new Tuple("Table", tableName)); + } + } + + private void ValidateDbIndexes(DatabaseSchemaResult result) + { + //These are just column indexes NOT constraints or Keys + var colIndexesInDatabase = result.DbIndexDefinitions.Where(x => x.IndexName.InvariantStartsWith("IX_")).Select(x => x.IndexName).ToList(); + var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); + + //Add valid and invalid index differences to the result object + var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var index in validColIndexDifferences) + { + result.ValidIndexes.Add(index); + } + + var invalidColIndexDifferences = + colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var index in invalidColIndexDifferences) + { + result.Errors.Add(new Tuple("Index", index)); + } } #region Events diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 49f9746b7c..4db0f7222a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -17,6 +17,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial ValidTables = new List(); ValidColumns = new List(); ValidConstraints = new List(); + ValidIndexes = new List(); } public List> Errors { get; set; } @@ -29,6 +30,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial public List ValidConstraints { get; set; } + public List ValidIndexes { get; set; } + internal IEnumerable DbIndexDefinitions { get; set; } /// @@ -45,7 +48,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(0, 0, 0); //If Errors is empty or if TableDefinitions tables + columns correspond to valid tables + columns then we're at current version - if (!Errors.Any() || + if (Errors.Any() == false || (TableDefinitions.All(x => ValidTables.Contains(x.Name)) && TableDefinitions.SelectMany(definition => definition.Columns).All(x => ValidColumns.Contains(x.Name)))) return UmbracoVersion.Current; @@ -71,6 +74,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(6, 0, 0); } + //if the error is for an index + if (Errors.Any(x => x.Item1.Equals("Index") && (x.Item2.InvariantEquals("IX_umbracoNodeTrashed")))) + { + return new Version(6, 1, 0); + } + return UmbracoVersion.Current; } @@ -108,6 +117,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Constraint")).Select(x => x.Item2))); sb.AppendLine(" "); } + //Index error summary + if (Errors.Any(x => x.Item1.Equals("Index"))) + { + sb.AppendLine("The following indexes were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Index")).Select(x => x.Item2))); + sb.AppendLine(" "); + } //Unknown constraint error summary if (Errors.Any(x => x.Item1.Equals("Unknown"))) { diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs index b815896539..f49bea5ce0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Migrations /// database migrations with Up/Down methods for pushing changes UP or pulling them DOWN. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class MigrationAttribute : Attribute + public sealed class MigrationAttribute : Attribute { public MigrationAttribute(string targetVersion, int sortOrder, string product) { diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index a988d77740..b4117003fb 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase; namespace Umbraco.Core.Persistence.Migrations { - /// + /// /// Represents the Migration Runner, which is used to apply migrations to /// the umbraco database. /// @@ -45,19 +46,62 @@ namespace Umbraco.Core.Persistence.Migrations { LogHelper.Info("Initializing database migrations"); - var foundMigrations = MigrationResolver.Current.Migrations; + var foundMigrations = MigrationResolver.Current.Migrations.ToArray(); - var migrations = isUpgrade - ? OrderedUpgradeMigrations(foundMigrations).ToList() - : OrderedDowngradeMigrations(foundMigrations).ToList(); + //filter all schema migrations + var schemaMigrations = isUpgrade + ? OrderedUpgradeMigrations(foundMigrations.Where(x => (x is SchemaMigration))).ToList() + : OrderedDowngradeMigrations(foundMigrations.Where(x => (x is SchemaMigration))).ToList(); + + //filter all non-schema migrations + var dataMigrations = isUpgrade + ? OrderedUpgradeMigrations(foundMigrations.Where(x => (x is SchemaMigration) == false)).ToList() + : OrderedDowngradeMigrations(foundMigrations.Where(x => (x is SchemaMigration) == false)).ToList(); //SD: Why do we want this? - if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _configuredVersion, _targetVersion, true), this)) + if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(dataMigrations, _configuredVersion, _targetVersion, true), this)) return false; //Loop through migrations to generate sql - var context = ExecuteMigrations(migrations, database, databaseProvider, isUpgrade); + var schemaMigrationContext = InitializeMigrations(schemaMigrations, database, databaseProvider, isUpgrade); + + try + { + ExecuteMigrations(schemaMigrationContext, database); + } + catch (Exception) + { + //if this fails then the transaction will be rolled back, BUT if we are using MySql this is not the case, + //since it does not support schema changes in a transaction, see: http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html + //so in that case we have to downgrade + if (databaseProvider == DatabaseProviders.MySql) + { + var downgrades = OrderedDowngradeMigrations(foundMigrations.Where(x => (x is SchemaMigration))).ToList(); + var downgradeMigrationContext = InitializeMigrations(downgrades, database, databaseProvider, false); + //lets hope that works! - if something cannot be rolled back then a CatastrophicDataLossException should + // be thrown. + ExecuteMigrations(downgradeMigrationContext, database); + } + + //continue throwing the exception + throw; + } + + //Ok, we've made it this far, now we can execute our data migrations + + //Loop through migrations to generate sql + var dataMigrationContext = InitializeMigrations(dataMigrations, database, databaseProvider, isUpgrade); + //run them - if this fails the data will be rolled back + ExecuteMigrations(dataMigrationContext, database); + + Migrated.RaiseEvent(new MigrationEventArgs(dataMigrations, dataMigrationContext, _configuredVersion, _targetVersion, false), this); + + return true; + } + + private void ExecuteMigrations(IMigrationContext context, Database database) + { //Transactional execution of the sql that was generated from the found migrations using (var transaction = database.GetTransaction()) { @@ -78,13 +122,9 @@ namespace Umbraco.Core.Persistence.Migrations transaction.Complete(); } + } - Migrated.RaiseEvent(new MigrationEventArgs(migrations, context, _configuredVersion, _targetVersion, false), this); - - return true; - } - - internal MigrationContext ExecuteMigrations(List migrations, Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) + internal MigrationContext InitializeMigrations(List migrations, Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) { //Loop through migrations to generate sql var context = new MigrationContext(databaseProvider, database); diff --git a/src/Umbraco.Core/Persistence/Migrations/SchemaMigration.cs b/src/Umbraco.Core/Persistence/Migrations/SchemaMigration.cs new file mode 100644 index 0000000000..6c1e9301ae --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/SchemaMigration.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// A migration class that specifies that it is used for db schema migrations only - these need to execute first and MUST + /// have a downgrade plan. + /// + public abstract class SchemaMigration : MigrationBase + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs index efcb5d2415..e7543fd063 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Configuration; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero { [MigrationAttribute("4.9.0", 0, GlobalSettings.UmbracoMigrationName)] - public class RemoveUmbracoAppConstraints : MigrationBase + public class RemoveUmbracoAppConstraints : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs index 567f641e5c..fe0820a025 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { + [Migration("6.0.0", 10, GlobalSettings.UmbracoMigrationName)] public class DeleteAppTables : MigrationBase { @@ -14,6 +15,8 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix public override void Down() { + //This cannot be rolled back!! + throw new CatastrophicDataLossException("Cannot rollback migration " + typeof(DeleteAppTables) + " the db tables umbracoAppTree and umbracoApp have been droppped"); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs index 7ff03087c4..29fb8c0f6d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 4, GlobalSettings.UmbracoMigrationName)] - public class NewCmsContentType2ContentTypeTable : MigrationBase + public class NewCmsContentType2ContentTypeTable : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs index 680b2001a0..b11c32e818 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 6, GlobalSettings.UmbracoMigrationName)] - public class RemoveMasterContentTypeColumn : MigrationBase + public class RemoveMasterContentTypeColumn : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs index 0f8635af09..daf5400c81 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 0, GlobalSettings.UmbracoMigrationName)] - public class RenameCmsTabTable : MigrationBase + public class RenameCmsTabTable : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs index 6f3f7edc80..7ff33092b0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Configuration; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 7, GlobalSettings.UmbracoMigrationName)] - public class RenameTabIdColumn : MigrationBase + public class RenameTabIdColumn : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs index be704466a8..0d9ddef431 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 3, GlobalSettings.UmbracoMigrationName)] - public class UpdateCmsContentTypeAllowedContentTypeTable : MigrationBase + public class UpdateCmsContentTypeAllowedContentTypeTable : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs index 3e6b4cf05c..935b6f62bf 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 2, GlobalSettings.UmbracoMigrationName)] - public class UpdateCmsContentTypeTable : MigrationBase + public class UpdateCmsContentTypeTable : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs index 1dc8998e68..e84383d753 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 8, GlobalSettings.UmbracoMigrationName)] - public class UpdateCmsContentVersionTable : MigrationBase + public class UpdateCmsContentVersionTable : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs index 2bd03f15bc..ab7f0fa5c1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Configuration; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix { [Migration("6.0.0", 1, GlobalSettings.UmbracoMigrationName)] - public class UpdateCmsPropertyTypeGroupTable : MigrationBase + public class UpdateCmsPropertyTypeGroupTable : SchemaMigration { public override void Up() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs index 757e5fd4f0..e54e43761d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs @@ -9,15 +9,17 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixOneZero { [Migration("6.1.0", 0, GlobalSettings.UmbracoMigrationName)] - public class CreateServerRegistryTable : MigrationBase + public class CreateServerRegistryTable : SchemaMigration { public override void Up() { - base.Context.Database.CreateTable(); + //NOTE: This isn't the correct way to do this but to manually create this table with the Create syntax is a pain in the arse + Context.Database.CreateTable(); } public override void Down() { + Delete.Table("umbracoServer"); } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs index 884829affc..146124dccb 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { [Migration("6.2.0", 0, GlobalSettings.UmbracoMigrationName)] - public class AdditionalIndexesAndKeys : MigrationBase + public class AdditionalIndexesAndKeys : SchemaMigration { public override void Up() { @@ -44,22 +44,10 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero public override void Down() { - throw new NotImplementedException(); - } - } - - [Migration("6.2.0", 2, GlobalSettings.UmbracoMigrationName)] - public class ChangePasswordColumn : MigrationBase - { - public override void Up() - { - //up to 500 chars - Alter.Table("umbracoUser").AlterColumn("userPassword").AsString(500).NotNullable(); - } - - public override void Down() - { - throw new NotImplementedException(); + Delete.Index("IX_umbracoNodeTrashed").OnTable("umbracoNode"); + Delete.Index("IX_cmsContentVersion_ContentId").OnTable("cmsContentVersion"); + Delete.Index("IX_cmsDocument_published").OnTable("cmsDocument"); + Delete.Index("IX_cmsDocument_newest").OnTable("cmsDocument"); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs new file mode 100644 index 0000000000..545e74f1e6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs @@ -0,0 +1,21 @@ +using System; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero +{ + [Migration("6.2.0", 2, GlobalSettings.UmbracoMigrationName)] + public class ChangePasswordColumn : SchemaMigration + { + public override void Up() + { + //up to 500 chars + Alter.Table("umbracoUser").AlterColumn("userPassword").AsString(500).NotNullable(); + } + + public override void Down() + { + //back to 125 chars + Alter.Table("umbracoUser").AlterColumn("userPassword").AsString(125).NotNullable(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs index abc312ab5d..66be6bbe30 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs @@ -34,10 +34,10 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero const string propertyTypeUpdateSql = @"UPDATE cmsPropertyType SET Alias = @newAlias WHERE Alias = @oldAlias AND contentTypeId IN ( -SELECT DISTINCT cmsContentType.nodeId FROM cmsPropertyType +SELECT nodeId FROM (SELECT DISTINCT cmsContentType.nodeId FROM cmsPropertyType INNER JOIN cmsContentType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE umbracoNode.nodeObjectType = @objectType)"; +WHERE umbracoNode.nodeObjectType = @objectType) x)"; const string xmlSelectSql = @"SELECT cmsContentXml.* FROM cmsContentXml INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index f50b864203..7e8d7b7566 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -134,10 +134,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax db.OpenSharedConnection(); var indexes = - db.Fetch(@"SELECT DISTINCT - TABLE_NAME, INDEX_NAME, COLUMN_NAME, CASE NON_UNIQUE WHEN 1 THEN 0 ELSE 1 END AS `UNIQUE` -FROM INFORMATION_SCHEMA.STATISTICS + db.Fetch(@"SELECT DISTINCT + TABLE_NAME, INDEX_NAME, COLUMN_NAME, CASE NON_UNIQUE WHEN 1 THEN 0 ELSE 1 END AS `UNIQUE` +FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = @TableSchema +AND INDEX_NAME <> COLUMN_NAME AND INDEX_NAME <> 'PRIMARY' ORDER BY TABLE_NAME, INDEX_NAME", new { TableSchema = db.Connection.Database }); list = diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 769d5547f4..170b10cb5e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -230,7 +230,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax { var items = db.Fetch( - "SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, [UNIQUE] FROM INFORMATION_SCHEMA.INDEXES ORDER BY TABLE_NAME, INDEX_NAME"); + @"SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, [UNIQUE] FROM INFORMATION_SCHEMA.INDEXES +WHERE INDEX_NAME NOT LIKE 'PK_%' +ORDER BY TABLE_NAME, INDEX_NAME"); return items.Select( item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE)); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 00311d6af6..ca9e88fb1e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -160,6 +160,7 @@ CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [U from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id] inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id] inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id] +WHERE I.name NOT LIKE 'PK_%' order by T.name, I.name"); return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)).ToList(); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index fb9cbad2a3..549c1378bc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -214,6 +214,9 @@ + + + diff --git a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs index 6964a5b8a7..dd28e559bb 100644 --- a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Migrations var migrations = runner.OrderedUpgradeMigrations(new List {new MultiMigration()}); - var ctx = runner.ExecuteMigrations( + var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, migrations.ToList(), new Database("umbracoDbDSN") @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Migrations var migrations = runner.OrderedUpgradeMigrations(new List { new MultiMigration() }); - var ctx = runner.ExecuteMigrations( + var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, migrations.ToList(), new Database("umbracoDbDSN") @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Migrations var migrations = runner.OrderedUpgradeMigrations(new List { new MultiMigration() }); - var ctx = runner.ExecuteMigrations( + var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, migrations.ToList(), new Database("umbracoDbDSN") diff --git a/src/Umbraco.Web.UI/install/Default.aspx.cs b/src/Umbraco.Web.UI/install/Default.aspx.cs index 5ce92070a8..af37f0c76c 100644 --- a/src/Umbraco.Web.UI/install/Default.aspx.cs +++ b/src/Umbraco.Web.UI/install/Default.aspx.cs @@ -60,8 +60,9 @@ namespace Umbraco.Web.UI.Install _installStep = Request.GetItemAsString("installStep"); //if this is not an upgrade we will log in with the default user. - // It's not considered an upgrade if the ConfigurationStatus is missing or empty. - if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus) == false) + // It's not considered an upgrade if the ConfigurationStatus is missing or empty or if the db is not configured (initially). + if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus) == false + && (IsPostBack == false && global::Umbraco.Core.ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)) { var result = Security.ValidateCurrentUser(new HttpContextWrapper(Context));