//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlTools.Utility;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.SqlScriptPublish;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using System.Diagnostics;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
///
/// Class to generate script as for one smo object
///
public class ScriptAsScriptingOperation : SmoScriptingOperation
{
private readonly IScripter _scripter;
private static readonly Dictionary scriptCompatibilityMap = LoadScriptCompatibilityMap();
private string _serverName;
private string _databaseName;
public ScriptAsScriptingOperation(ScriptingParams parameters, IScripter scripter, IDataSource datasource) :
base(parameters, datasource)
{
_scripter = scripter;
}
public override void Execute()
{
try
{
this.CancellationToken.ThrowIfCancellationRequested();
this.ValidateScriptDatabaseParams();
this.CancellationToken.ThrowIfCancellationRequested();
string resultScript = string.Empty;
UrnCollection urns = CreateUrns(_dataSource);
ScriptingOptions options = new ScriptingOptions();
SetScriptBehavior(options);
ScriptAsOptions scriptAsOptions = new ScriptAsOptions(this.Parameters.ScriptOptions);
PopulateAdvancedScriptOptions(scriptAsOptions, options);
options.WithDependencies = false;
// TODO: Not including the header by default. We have to get this option from client
options.IncludeHeaders = false;
// Scripting data is not available in the scripter
options.ScriptData = false;
SetScriptingOptions(options);
switch (this.Parameters.Operation)
{
case ScriptingOperationType.Select:
resultScript = GenerateScriptSelect(_dataSource, urns);
break;
case ScriptingOperationType.Alter:
case ScriptingOperationType.Execute:
resultScript = GenerateScriptForFunction(_dataSource);
break;
}
this.CancellationToken.ThrowIfCancellationRequested();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending script complete notification event for operation {0}",
this.OperationId
));
ScriptText = resultScript;
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
Success = true,
});
this.SendPlanNotificationEvent(new ScriptingPlanNotificationParams
{
ScriptingObjects = this.Parameters.ScriptingObjects,
Count = 1,
});
}
catch (Exception e)
{
if (e.IsOperationCanceledException())
{
Logger.Write(TraceEventType.Information, string.Format("Scripting operation {0} was canceled", this.OperationId));
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
Canceled = true,
});
}
else
{
Logger.Write(TraceEventType.Error, string.Format("Scripting operation {0} failed with exception {1}", this.OperationId, e));
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
OperationId = OperationId,
HasError = true,
ErrorMessage = $"{SR.ScriptingGeneralError} {e.Message}",
ErrorDetails = e.ToString(),
});
}
}
}
private string GenerateScriptSelect(IDataSource dataSource, UrnCollection urns)
{
ScriptingObject scriptingObject = this.Parameters.ScriptingObjects[0];
Urn objectUrn = urns[0];
// select from table
if (string.Equals(scriptingObject.Type, "Table", StringComparison.CurrentCultureIgnoreCase))
{
return _scripter.SelectFromTableOrView(dataSource, objectUrn);
}
return string.Empty;
}
private string GenerateScriptForFunction(IDataSource dataSource)
{
ScriptingObject scriptingObject = this.Parameters.ScriptingObjects[0];
if (!string.Equals(scriptingObject.Type, "Function", StringComparison.CurrentCultureIgnoreCase))
{
return string.Empty;
}
if (Parameters.Operation == ScriptingOperationType.Alter)
{
return _scripter.AlterFunction(dataSource, scriptingObject);
}
if (Parameters.Operation == ScriptingOperationType.Execute)
{
return _scripter.ExecuteFunction(dataSource, scriptingObject);
}
return string.Empty;
}
private UrnCollection CreateUrns(IDataSource dataSource)
{
IEnumerable selectedObjects = new List(this.Parameters.ScriptingObjects);
_serverName = dataSource.ClusterName;
_databaseName = new SqlConnectionStringBuilder(this.Parameters.ConnectionString).InitialCatalog;
UrnCollection urnCollection = new UrnCollection();
foreach (var scriptingObject in selectedObjects)
{
if(string.IsNullOrEmpty(scriptingObject.Schema))
{
// TODO: get the default schema
scriptingObject.Schema = "dbo";
}
urnCollection.Add(scriptingObject.ToUrn(_serverName, _databaseName));
}
return urnCollection;
}
private void SetScriptBehavior(ScriptingOptions options)
{
// TODO: have to add Scripting behavior to Smo ScriptingOptions class
// so it would support ScriptDropAndScreate
switch (this.Parameters.ScriptOptions.ScriptCreateDrop)
{
case "ScriptCreate":
options.ScriptDrops = false;
break;
case "ScriptDrop":
options.ScriptDrops = true;
break;
default:
options.ScriptDrops = false;
break;
}
}
private static Dictionary LoadScriptCompatibilityMap()
{
return new Dictionary
{
{SqlScriptOptions.ScriptCompatibilityOptions.Script150Compat.ToString(), SqlServerVersion.Version150},
{SqlScriptOptions.ScriptCompatibilityOptions.Script140Compat.ToString(), SqlServerVersion.Version140},
{SqlScriptOptions.ScriptCompatibilityOptions.Script130Compat.ToString(), SqlServerVersion.Version130},
{SqlScriptOptions.ScriptCompatibilityOptions.Script120Compat.ToString(), SqlServerVersion.Version120},
{SqlScriptOptions.ScriptCompatibilityOptions.Script110Compat.ToString(), SqlServerVersion.Version110},
{SqlScriptOptions.ScriptCompatibilityOptions.Script105Compat.ToString(), SqlServerVersion.Version105},
{SqlScriptOptions.ScriptCompatibilityOptions.Script100Compat.ToString(), SqlServerVersion.Version100},
{SqlScriptOptions.ScriptCompatibilityOptions.Script90Compat.ToString(), SqlServerVersion.Version90}
};
}
private void SetScriptingOptions(ScriptingOptions scriptingOptions)
{
scriptingOptions.AllowSystemObjects = true;
// setting this forces SMO to correctly script objects that have been renamed
scriptingOptions.EnforceScriptingOptions = true;
//We always want role memberships for users and database roles to be scripted
scriptingOptions.IncludeDatabaseRoleMemberships = true;
SqlServerVersion targetServerVersion;
if(scriptCompatibilityMap.TryGetValue(this.Parameters.ScriptOptions.ScriptCompatibilityOption, out targetServerVersion))
{
scriptingOptions.TargetServerVersion = targetServerVersion;
}
else
{
//If you are getting this assertion fail it means you are working for higher
//version of SQL Server. You need to update this part of code.
Logger.Write(TraceEventType.Warning, "This part of the code is not updated corresponding to latest version change");
}
// for cloud scripting to work we also have to have Script Compat set to 105.
// the defaults from scripting options should take care of it
object targetDatabaseEngineType;
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptDatabaseEngineType), this.Parameters.ScriptOptions.TargetDatabaseEngineType, out targetDatabaseEngineType))
{
switch ((SqlScriptOptions.ScriptDatabaseEngineType)targetDatabaseEngineType)
{
case SqlScriptOptions.ScriptDatabaseEngineType.SingleInstance:
scriptingOptions.TargetDatabaseEngineType = DatabaseEngineType.Standalone;
break;
case SqlScriptOptions.ScriptDatabaseEngineType.SqlAzure:
scriptingOptions.TargetDatabaseEngineType = DatabaseEngineType.SqlAzureDatabase;
break;
}
}
object targetDatabaseEngineEdition;
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptDatabaseEngineEdition), this.Parameters.ScriptOptions.TargetDatabaseEngineEdition, out targetDatabaseEngineEdition))
{
switch ((SqlScriptOptions.ScriptDatabaseEngineEdition)targetDatabaseEngineEdition)
{
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerPersonalEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Personal;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerStandardEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Standard;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerEnterpriseEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Enterprise;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerExpressEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Express;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlAzureDatabaseEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlDatabase;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlDatawarehouseEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlDataWarehouse;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerStretchEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlStretchDatabase;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerManagedInstanceEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlManagedInstance;
break;
default:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Standard;
break;
}
}
if (scriptingOptions.TargetDatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse)
{
scriptingOptions.Triggers = false;
}
scriptingOptions.NoVardecimal = false; //making IncludeVarDecimal true for DPW
// scripting of stats is a combination of the Statistics
// and the OptimizerData flag
object scriptStatistics;
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptStatisticsOptions), this.Parameters.ScriptOptions.ScriptStatistics, out scriptStatistics))
{
switch ((SqlScriptOptions.ScriptStatisticsOptions)scriptStatistics)
{
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsAll:
scriptingOptions.Statistics = true;
scriptingOptions.OptimizerData = true;
break;
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsDDL:
scriptingOptions.Statistics = true;
scriptingOptions.OptimizerData = false;
break;
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsNone:
scriptingOptions.Statistics = false;
scriptingOptions.OptimizerData = false;
break;
}
}
// If Histogram and Update Statics are True then include DriIncludeSystemNames and AnsiPadding by default
if (scriptingOptions.Statistics == true && scriptingOptions.OptimizerData == true)
{
scriptingOptions.DriIncludeSystemNames = true;
scriptingOptions.AnsiPadding = true;
}
}
}
}