diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index a54b997cb8..ccf2365deb 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -33,6 +33,9 @@ namespace Umbraco.Core.Migrations.Install private DatabaseSchemaResult _databaseSchemaValidationResult; + /// + /// Initializes a new instance of the class. + /// public DatabaseBuilder(IScopeProvider scopeProvider, IGlobalSettings globalSettings, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtime, ILogger logger, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations) { _scopeProvider = scopeProvider; @@ -49,23 +52,20 @@ namespace Umbraco.Core.Migrations.Install /// /// Gets a value indicating whether the database is configured. It does not necessarily - /// mean that it is possible to connect, nor that Umbraco is installed, nor - /// up-to-date. + /// mean that it is possible to connect, nor that Umbraco is installed, nor up-to-date. /// public bool IsDatabaseConfigured => _databaseFactory.Configured; /// - /// Gets a value indicating whether it is possible to connect to the database. + /// Gets a value indicating whether it is possible to connect to the configured database. + /// It does not necessarily mean that Umbraco is installed, nor up-to-date. /// - public bool CanConnect => _databaseFactory.CanConnect; + public bool CanConnectToDatabase => _databaseFactory.CanConnect; - // that method was originally created by Per in DatabaseHelper- tests the db connection for install - // fixed by Shannon to not-ignore the provider - // fixed by Stephan as part of the v8 persistence cleanup, now using provider names + SqlCe exception - // moved by Stephan to DatabaseBuilder - // probably needs to be cleaned up - - public bool CheckConnection(string databaseType, string connectionString, string server, string database, string login, string password, bool integratedAuth) + /// + /// Verifies whether a it is possible to connect to a database. + /// + public bool CanConnect(string databaseType, string connectionString, string server, string database, string login, string password, bool integratedAuth) { // we do not test SqlCE connection if (databaseType.InvariantContains("sqlce")) @@ -93,7 +93,7 @@ namespace Umbraco.Core.Migrations.Install return DbConnectionExtensions.IsConnectionAvailable(connectionString, providerName); } - public bool HasSomeNonDefaultUser() + internal bool HasSomeNonDefaultUser() { using (var scope = _scopeProvider.CreateScope()) { @@ -417,17 +417,24 @@ namespace Umbraco.Core.Migrations.Install #region Database Schema - public DatabaseSchemaResult ValidateDatabaseSchema() + /// + /// Validates the database schema. + /// + /// + /// This assumes that the database exists and the connection string is + /// configured and it is possible to connect to the database. + /// + internal DatabaseSchemaResult ValidateSchema() { using (var scope = _scopeProvider.CreateScope()) { - var result = ValidateDatabaseSchema(scope); + var result = ValidateSchema(scope); scope.Complete(); return result; } } - private DatabaseSchemaResult ValidateDatabaseSchema(IScope scope) + private DatabaseSchemaResult ValidateSchema(IScope scope) { if (_databaseFactory.Configured == false) return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); @@ -442,17 +449,24 @@ namespace Umbraco.Core.Migrations.Install return _databaseSchemaValidationResult; } - public Result CreateDatabaseSchemaAndData() + /// + /// Creates the database schema and inserts initial data. + /// + /// + /// This assumes that the database exists and the connection string is + /// configured and it is possible to connect to the database. + /// + public Result CreateSchemaAndData() { using (var scope = _scopeProvider.CreateScope()) { - var result = CreateDatabaseSchemaAndData(scope); + var result = CreateSchemaAndData(scope); scope.Complete(); return result; } } - private Result CreateDatabaseSchemaAndData(IScope scope) + private Result CreateSchemaAndData(IScope scope) { try { @@ -468,28 +482,14 @@ namespace Umbraco.Core.Migrations.Install // If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks // for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive. - - /* - var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); - if (supportsCaseInsensitiveQueries == false) - { - message = "

 

The database you're trying to use does not support case insensitive queries.
We currently do not support these types of databases.

" + - "

You can fix this by changing the following setting in your my.ini file in your MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - - return new Result { Message = message, Success = false, Percentage = "15" }; - } - */ - - var message = GetResultMessageForMySql(); - var schemaResult = ValidateDatabaseSchema(); - var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); + var message = database.DatabaseType.IsMySql() ? ResultMessageForMySql : ""; + var schemaResult = ValidateSchema(); + var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion(); + //var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); + //var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0)); //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) + if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && !hasInstalledVersion) { if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); @@ -521,7 +521,14 @@ namespace Umbraco.Core.Migrations.Install } } - // This assumes all of the previous checks are done! + /// + /// Upgrades the database schema and data by running migrations. + /// + /// + /// This assumes that the database exists and the connection string is + /// configured and it is possible to connect to the database. + /// Runs whichever migrations need to run. + /// public Result UpgradeSchemaAndData() { try @@ -534,7 +541,7 @@ namespace Umbraco.Core.Migrations.Install _logger.Info("Database upgrade started"); - var message = GetResultMessageForMySql(); + var message = _scopeProvider.SqlContext.DatabaseType.IsMySql() ? ResultMessageForMySql : ""; // upgrade var upgrader = new UmbracoUpgrader(); @@ -554,47 +561,14 @@ namespace Umbraco.Core.Migrations.Install } } - private string GetResultMessageForMySql() - { - if (_databaseFactory.GetType() == typeof(MySqlSyntaxProvider)) - { - return "

 

Congratulations, the database step ran successfully!

" + - "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + - "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + - "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - return string.Empty; - } - - /* - private string GetResultMessageForMySql(bool? supportsCaseInsensitiveQueries) - { - if (supportsCaseInsensitiveQueries == null) - { - return "

 

Warning! Could not check if your database type supports case insensitive queries.
We currently do not support these databases that do not support case insensitive queries.

" + - "

You can check this by looking for the following setting in your my.ini file in your MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - if (SqlSyntax.GetType() == typeof(MySqlSyntaxProvider)) - { - return "

 

Congratulations, the database step ran successfully!

" + - "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + - "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + - "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - return string.Empty; - }*/ + private const string ResultMessageForMySql = "

 

Congratulations, the database step ran successfully!

" + + "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + + "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + + "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; private Attempt CheckReadyForInstall() { @@ -630,11 +604,29 @@ namespace Umbraco.Core.Migrations.Install }; } + /// + /// Represents the result of a database creation or upgrade. + /// public class Result { + /// + /// Gets or sets ... fixme + /// public bool RequiresUpgrade { get; set; } + + /// + /// Gets or sets the message returned by the operation. + /// public string Message { get; set; } + + /// + /// Gets or sets a value indicating whether the operation succeeded. + /// public bool Success { get; set; } + + /// + /// Gets or sets ... fixme + /// public string Percentage { get; set; } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index 64be8161f2..eba5e61390 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -138,9 +138,8 @@ namespace Umbraco.Core.Migrations.Install { var result = new DatabaseSchemaResult(SqlSyntax); - //get the db index defs - result.DbIndexDefinitions = SqlSyntax.GetDefinedIndexes(_database) - .Select(x => new DbIndexDefinition(x)).ToArray(); + result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) + .Select(x => new DbIndexDefinition(x))); result.TableDefinitions.AddRange(OrderedTables .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); @@ -279,7 +278,7 @@ namespace Umbraco.Core.Migrations.Install { //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 colIndexesInDatabase = result.DbIndexDefinitions.Select(x => x.IndexName).ToList(); + var colIndexesInDatabase = result.IndexDefinitions.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 diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs index 0ec27cf0b1..4c68addebc 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs @@ -2,153 +2,55 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Migrations.Install { - public class DatabaseSchemaResult + /// + /// Represents ... + /// + internal class DatabaseSchemaResult { - private readonly ISqlSyntaxProvider _sqlSyntax; + private readonly bool _isMySql; public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax) { - _sqlSyntax = sqlSyntax; + _isMySql = sqlSyntax is MySqlSyntaxProvider; + Errors = new List>(); TableDefinitions = new List(); ValidTables = new List(); ValidColumns = new List(); ValidConstraints = new List(); ValidIndexes = new List(); + IndexDefinitions = new List(); } - public List> Errors { get; set; } + public List> Errors { get; } - public List TableDefinitions { get; set; } + public List TableDefinitions { get; } - public List ValidTables { get; set; } + // fixme TableDefinitions are those that should be there, IndexDefinitions are those that... are in DB? + internal List IndexDefinitions { get; } - public List ValidColumns { get; set; } + public List ValidTables { get; } - public List ValidConstraints { get; set; } + public List ValidColumns { get; } - public List ValidIndexes { get; set; } + public List ValidConstraints { get; } - internal IEnumerable DbIndexDefinitions { get; set; } + public List ValidIndexes { get; } /// - /// Determines the version of the currently installed database by detecting the current database structure + /// Determines whether the database contains an installed version. /// - /// - /// A with Major and Minor values for - /// non-empty database, otherwise "0.0.0" for empty databases. - /// - public Version DetermineInstalledVersion() + /// + /// A database contains an installed version when it contains at least one valid table. + /// + public bool DetermineHasInstalledVersion() { - // v8 = kill versions older than 7 - - //If (ValidTables.Count == 0) database is empty and we return -> new Version(0, 0, 0); - if (ValidTables.Count == 0) - return new Version(0, 0, 0); - - // FIXME - but the whole detection is borked really - return new Version(8, 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() == false || - (TableDefinitions.All(x => ValidTables.Contains(x.Name)) - && TableDefinitions.SelectMany(definition => definition.Columns).All(x => ValidColumns.Contains(x.Name)))) - return UmbracoVersion.Current; - - //If Errors contains umbracoApp or umbracoAppTree its pre-6.0.0 -> new Version(4, 10, 0); - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoApp") || x.Item2.InvariantEquals("umbracoAppTree")))) - { - //If Errors contains umbracoUser2app or umbracoAppTree foreignkey to umbracoApp exists its pre-4.8.0 -> new Version(4, 7, 0); - if (Errors.Any(x => - x.Item1.Equals("Constraint") - && (x.Item2.InvariantContains("umbracoUser2app_umbracoApp") - || x.Item2.InvariantContains("umbracoAppTree_umbracoApp")))) - { - return new Version(4, 7, 0); - } - - return new Version(4, 8, 0); - } - - //if the error is for umbracoServer - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoServer")))) - { - return new Version(6, 0, 0); - } - - //if the error indicates a problem with the column cmsMacroProperty.macroPropertyType then it is not version 7 - // since these columns get removed in v7 - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,macroPropertyType")))) - { - //if the error is for this IX_umbracoNodeTrashed which is added in 6.2 AND in 7.1 but we do not have the above columns - // then it must mean that we aren't on 6.2 so must be 6.1 - if (Errors.Any(x => x.Item1.Equals("Index") && (x.Item2.InvariantEquals("IX_umbracoNodeTrashed")))) - { - return new Version(6, 1, 0); - } - else - { - //if there are no errors for that index, then the person must have 6.2 installed - return new Version(6, 2, 0); - } - } - - //if the error indicates a problem with the constraint FK_cms-OBSOLETE-Content_cmsContentType_nodeId then it is not version 7.2 - // since this gets added in 7.2.0 so it must be the previous version - if (Errors.Any(x => x.Item1.Equals("Constraint") && (x.Item2.InvariantEquals("FK_cms-OBSOLETE-Content_cmsContentType_nodeId")))) - { - return new Version(7, 0, 0); - } - - //if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess")))) - { - return new Version(7, 2, 0); - } - - //if the error is for cms-OBSOLETE-PropertyData.dataDecimal it must be the previous version to 7.4 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cms-OBSOLETE-PropertyData,dataDecimal")))) - { - return new Version(7, 3, 0); - } - - //if the error is for umbracoRedirectUrl it must be the previous version to 7.5 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoRedirectUrl")))) - { - return new Version(7, 4, 0); - } - - //if the error indicates a problem with the column cmsMacroProperty.uniquePropertyId then it is not version 7.6 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,uniquePropertyId")))) - { - return new Version(7, 5, 0); - } - - //if the error is for umbracoUserGroup it must be the previous version to 7.7 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserStartNode")))) - { - return new Version(7, 6, 0); - } - - //if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoMedia")))) - { - return new Version(7, 7, 0); - } - - //if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive")))) - { - return new Version(7, 8, 0); - } - - return UmbracoVersion.Current; + return ValidTables.Count > 0; } /// @@ -200,9 +102,9 @@ namespace Umbraco.Core.Migrations.Install sb.AppendLine(" "); } - if (_sqlSyntax is MySqlSyntaxProvider) + if (_isMySql) { - sb.AppendLine("Please note that the constraints could not be validated because the current dataprovider is MySql."); + sb.AppendLine("Please note that the constraints could not be validated because the current data provider is MySql."); } return sb.ToString(); diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs index b8f1fab918..2c875d6afc 100644 --- a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs +++ b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs @@ -26,7 +26,6 @@ namespace Umbraco.Tests.Persistence // Assert Assert.That(result.Errors.Count, Is.EqualTo(0)); - Assert.AreEqual(result.DetermineInstalledVersion(), UmbracoVersion.Current); } } } diff --git a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs index 387360163a..81b1aac217 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Install.Controllers public bool PostValidateDatabaseConnection(DatabaseModel model) { - var canConnect = _databaseBuilder.CheckConnection(model.DatabaseType.ToString(), model.ConnectionString, model.Server, model.DatabaseName, model.Login, model.Password, model.IntegratedAuth); + var canConnect = _databaseBuilder.CanConnect(model.DatabaseType.ToString(), model.ConnectionString, model.Server, model.DatabaseName, model.Login, model.Password, model.IntegratedAuth); return canConnect; } diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index a54b64733f..2fe6c0ceda 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -1,7 +1,6 @@ using System; using System.Configuration; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Web.Install.Models; @@ -29,7 +28,7 @@ namespace Umbraco.Web.Install.InstallSteps database = new DatabaseModel(); } - if (_databaseBuilder.CheckConnection(database.DatabaseType.ToString(), database.ConnectionString, database.Server, database.DatabaseName, database.Login, database.Password, database.IntegratedAuth) == false) + if (_databaseBuilder.CanConnect(database.DatabaseType.ToString(), database.ConnectionString, database.Server, database.DatabaseName, database.Login, database.Password, database.IntegratedAuth) == false) { throw new InstallException("Could not connect to the database"); } @@ -79,8 +78,7 @@ namespace Umbraco.Web.Install.InstallSteps try { //Since a connection string was present we verify the db can connect and query - var result = _databaseBuilder.ValidateDatabaseSchema(); - result.DetermineInstalledVersion(); + _ = _databaseBuilder.ValidateSchema(); return false; } catch (Exception ex) diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs index c4cad38072..a9daee6e95 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Install.InstallSteps if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); - var result = _databaseBuilder.CreateDatabaseSchemaAndData(); + var result = _databaseBuilder.CreateSchemaAndData(); if (result.Success == false) { diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs index c078caf906..8283eb6bef 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -63,18 +63,10 @@ namespace Umbraco.Web.Install.InstallSteps if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings)) { - //Since a connection string was present we verify whether this is an upgrade or an empty db - var result = _databaseBuilder.ValidateDatabaseSchema(); - - var determinedVersion = result.DetermineInstalledVersion(); - if (determinedVersion.Equals(new Version(0, 0, 0))) - { - //Fresh install - return false; - } - - //Upgrade - return true; + // a connection string was present, determine whether this is an install/upgrade + // return true (upgrade) if there is an installed version, else false (install) + var result = _databaseBuilder.ValidateSchema(); + return result.DetermineHasInstalledVersion(); } //no connection string configured, probably a fresh install