using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Initial
{
///
/// Represents the initial database schema creation by running CreateTable for all DTOs against the db.
///
internal class DatabaseSchemaCreation
{
#region Private Members
private readonly Database _database;
private static readonly Dictionary OrderedTables = new Dictionary
{
{0, typeof (NodeDto)},
{1, typeof (TemplateDto)},
{2, typeof (ContentDto)},
{3, typeof (ContentVersionDto)},
{4, typeof (DocumentDto)},
{5, typeof (ContentTypeDto)},
{6, typeof (DocumentTypeDto)},
{7, typeof (DataTypeDto)},
{8, typeof (DataTypePreValueDto)},
{9, typeof (DictionaryDto)},
{10, typeof (LanguageTextDto)},
{11, typeof (LanguageDto)},
{12, typeof (DomainDto)},
{13, typeof (LogDto)},
{14, typeof (MacroDto)},
{15, typeof (MacroPropertyTypeDto)},
{16, typeof (MacroPropertyDto)},
{17, typeof (MemberTypeDto)},
{18, typeof (MemberDto)},
{19, typeof (Member2MemberGroupDto)},
{20, typeof (ContentXmlDto)},
{21, typeof (PreviewXmlDto)},
{22, typeof (PropertyTypeGroupDto)},
{23, typeof (PropertyTypeDto)},
{24, typeof (PropertyDataDto)},
{25, typeof (RelationTypeDto)},
{26, typeof (RelationDto)},
{27, typeof (StylesheetDto)},
{28, typeof (StylesheetPropertyDto)},
{29, typeof (TagDto)},
{30, typeof (TagRelationshipDto)},
{31, typeof (UserLoginDto)},
{32, typeof (UserTypeDto)},
{33, typeof (UserDto)},
{34, typeof (TaskTypeDto)},
{35, typeof (TaskDto)},
{36, typeof (ContentType2ContentTypeDto)},
{37, typeof (ContentTypeAllowedContentTypeDto)},
{38, typeof (User2AppDto)},
{39, typeof (User2NodeNotifyDto)},
{40, typeof (User2NodePermissionDto)},
{41, typeof (ServerRegistrationDto)}
};
#endregion
///
/// Drops all Umbraco tables in the db
///
internal void UninstallDatabaseSchema()
{
LogHelper.Info("Start UninstallDatabaseSchema");
foreach (var item in OrderedTables.OrderByDescending(x => x.Key))
{
var tableNameAttribute = item.Value.FirstAttribute();
string tableName = tableNameAttribute == null ? item.Value.Name : tableNameAttribute.Value;
LogHelper.Info("Uninstall" + tableName);
try
{
if (_database.TableExist(tableName))
{
_database.DropTable(tableName);
}
}
catch (Exception ex)
{
//swallow this for now, not sure how best to handle this with diff databases... though this is internal
// and only used for unit tests. If this fails its because the table doesn't exist... generally!
LogHelper.Error("Could not drop table " + tableName, ex);
}
}
}
public DatabaseSchemaCreation(Database database)
{
_database = database;
}
///
/// Initialize the database by creating the umbraco db schema
///
public void InitializeDatabaseSchema()
{
var e = new DatabaseCreationEventArgs();
FireBeforeCreation(e);
if (!e.Cancel)
{
foreach (var item in OrderedTables.OrderBy(x => x.Key))
{
_database.CreateTable(false, item.Value);
}
}
FireAfterCreation(e);
}
///
/// Validates the schema of the current database
///
public DatabaseSchemaResult ValidateSchema()
{
var result = new DatabaseSchemaResult();
//get the db index defs
result.DbIndexDefinitions = SqlSyntaxContext.SqlSyntaxProvider.GetDefinedIndexes(_database)
.Select(x => new DbIndexDefinition()
{
TableName = x.Item1,
IndexName = x.Item2,
ColumnName = x.Item3,
IsUnique = x.Item4
}).ToArray();
foreach (var item in OrderedTables.OrderBy(x => x.Key))
{
var tableDefinition = DefinitionFactory.GetTableDefinition(item.Value);
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);
foreach (var tableName in validTableDifferences)
{
result.ValidTables.Add(tableName);
}
var invalidTableDifferences =
tablesInDatabase.Except(tablesInSchema)
.Union(tablesInSchema.Except(tablesInDatabase));
foreach (var tableName in invalidTableDifferences)
{
result.Errors.Add(new Tuple("Table", tableName));
}
//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);
foreach (var column in validColumnDifferences)
{
result.ValidColumns.Add(column);
}
var invalidColumnDifferences = columnsPerTableInDatabase.Except(columnsPerTableInSchema);
foreach (var column in invalidColumnDifferences)
{
result.Errors.Add(new Tuple("Column", column));
}
//MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks.
//NOTE: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers.
if (SqlSyntaxContext.SqlSyntaxProvider is MySqlSyntaxProvider)
return result;
//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.StartsWith("FK_")).Select(x => x.Item3).ToList();
var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.StartsWith("PK_")).Select(x => x.Item3).ToList();
var indexesInDatabase = constraintsInDatabase.Where(x => x.Item3.StartsWith("IX_")).Select(x => x.Item3).ToList();
var unknownConstraintsInDatabase =
constraintsInDatabase.Where(
x =>
x.Item3.StartsWith("FK_") == false && x.Item3.StartsWith("PK_") == false &&
x.Item3.StartsWith("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)
{
if (foreignKeysInSchema.Contains(unknown) || primaryKeysInSchema.Contains(unknown) || indexesInSchema.Contains(unknown))
{
result.ValidConstraints.Add(unknown);
}
else
{
result.Errors.Add(new Tuple("Unknown", unknown));
}
}
var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema);
foreach (var foreignKey in validForeignKeyDifferences)
{
result.ValidConstraints.Add(foreignKey);
}
var invalidForeignKeyDifferences = foreignKeysInDatabase.Except(foreignKeysInSchema);
foreach (var foreignKey in invalidForeignKeyDifferences)
{
result.Errors.Add(new Tuple("Constraint", foreignKey));
}
//Add valid and invalid primary key differences to the result object
var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema);
foreach (var primaryKey in validPrimaryKeyDifferences)
{
result.ValidConstraints.Add(primaryKey);
}
var invalidPrimaryKeyDifferences = primaryKeysInDatabase.Except(primaryKeysInSchema);
foreach (var primaryKey in invalidPrimaryKeyDifferences)
{
result.Errors.Add(new Tuple("Constraint", primaryKey));
}
//Add valid and invalid index differences to the result object
var validIndexDifferences = indexesInDatabase.Intersect(indexesInSchema);
foreach (var index in validIndexDifferences)
{
result.ValidConstraints.Add(index);
}
var invalidIndexDifferences = indexesInDatabase.Except(indexesInSchema);
foreach (var index in invalidIndexDifferences)
{
result.Errors.Add(new Tuple("Constraint", index));
}
return result;
}
#region Events
///
/// The save event handler
///
internal delegate void DatabaseEventHandler(DatabaseCreationEventArgs e);
///
/// Occurs when [before save].
///
internal static event DatabaseEventHandler BeforeCreation;
///
/// Raises the event.
///
/// The instance containing the event data.
protected internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e)
{
if (BeforeCreation != null)
{
BeforeCreation(e);
}
}
///
/// Occurs when [after save].
///
internal static event DatabaseEventHandler AfterCreation;
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void FireAfterCreation(DatabaseCreationEventArgs e)
{
if (AfterCreation != null)
{
AfterCreation(e);
}
}
#endregion
}
}