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());
}
}
}
}