using System; using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Migrations { /// /// Provides a base class for migration expressions. /// public abstract class MigrationExpressionBase : IMigrationExpression { private bool _executed; private List _expressions; protected MigrationExpressionBase(IMigrationContext context) { Context = context ?? throw new ArgumentNullException(nameof(context)); } protected IMigrationContext Context { get; } protected ILogger Logger => Context.Logger; protected ISqlSyntaxProvider SqlSyntax => Context.Database.SqlContext.SqlSyntax; protected IUmbracoDatabase Database => Context.Database; public DatabaseType DatabaseType => Context.Database.DatabaseType; public List Expressions => _expressions ?? (_expressions = new List()); protected virtual string GetSql() { return ToString(); } public void Execute() { if (_executed) throw new InvalidOperationException("This expression has already been executed."); _executed = true; Context.BuildingExpression = false; var sql = GetSql(); if (string.IsNullOrWhiteSpace(sql)) { Logger.LogInformation("SQL [{ContextIndex}]: ", Context.Index); } else { // split multiple statements - required for SQL CE // http://stackoverflow.com/questions/13665491/sql-ce-inconsistent-with-multiple-statements var stmtBuilder = new StringBuilder(); using (var reader = new StringReader(sql)) { string line; while ((line = reader.ReadLine()) != null) { if (line.Trim().Equals("GO", StringComparison.OrdinalIgnoreCase)) ExecuteStatement(stmtBuilder); else stmtBuilder.Append(line); } if (stmtBuilder.Length > 0) ExecuteStatement(stmtBuilder); } } Context.Index++; if (_expressions == null) return; foreach (var expression in _expressions) expression.Execute(); } protected void Execute(Sql sql) { if (_executed) throw new InvalidOperationException("This expression has already been executed."); _executed = true; Context.BuildingExpression = false; if (sql == null) { Logger.LogInformation($"SQL [{Context.Index}]: "); } else { Logger.LogInformation($"SQL [{Context.Index}]: {sql.ToText()}"); Database.Execute(sql); } Context.Index++; if (_expressions == null) return; foreach (var expression in _expressions) expression.Execute(); } private void ExecuteStatement(StringBuilder stmtBuilder) { var stmt = stmtBuilder.ToString(); Logger.LogInformation("SQL [{ContextIndex}]: {Sql}", Context.Index, stmt); Database.Execute(stmt); stmtBuilder.Clear(); } protected void AppendStatementSeparator(StringBuilder stmtBuilder) { stmtBuilder.AppendLine(";"); if (DatabaseType.IsSqlServerOrCe()) stmtBuilder.AppendLine("GO"); } /// /// This might be useful in the future if we add it to the interface, but for now it's used to hack the DeleteAppTables & DeleteForeignKeyExpression /// to ensure they are not executed twice. /// internal string Name { get; set; } protected string GetQuotedValue(object val) { if (val == null) return "NULL"; var type = val.GetType(); switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: return ((bool)val) ? "1" : "0"; case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: return val.ToString(); case TypeCode.DateTime: return SqlSyntax.GetQuotedValue(SqlSyntax.FormatDateTime((DateTime) val)); default: return SqlSyntax.GetQuotedValue(val.ToString()); } } } }