Merging mssql-scripter changes (#430)

* Manual port of latest 'feature/mssq-scripter' branch

* Bumpnuget package SqlScriptPublishModel.140.2.0 to Microsoft.SqlServer.Management.SqlScriptPublishModel.140.2.3

* In TestDriver, fix the path to Microsoft.SqlTools.ServiceLayer.exe after move to .NET Core 2.0
This commit is contained in:
Brian O'Neill
2017-08-09 08:25:13 -07:00
committed by GitHub
parent 1cbc78a266
commit a4a27f9559
11 changed files with 350 additions and 62 deletions

View File

@@ -130,6 +130,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// </remarks>
public string ScriptCompatibilityOption { get; set; } = "Script140Compat";
/// <summary>
/// Script only features compatible with the specified SQL Server database engine type.
/// Possible Values:
/// SingleInstance
/// SqlAzure
/// </summary>
public string TargetDatabaseEngineType { get; set; } = "SingleInstance";
/// <summary>
/// Script only features compatible with the specified SQL Server database engine edition.
/// Possible Values:
@@ -143,14 +151,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// </summary>
public string TargetDatabaseEngineEdition { get; set; } = "SqlServerEnterpriseEdition";
/// <summary>
/// Script only features compatible with the specified SQL Server database engine type.
/// Possible Values:
/// SingleInstance
/// SqlAzure
/// </summary>
public string TargetDatabaseEngineType { get; set; } = "SingleInstance";
/// <summary>
/// Script all logins available on the server. Passwords will not be scripted.
/// </summary>
@@ -224,7 +224,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// <summary>
/// Script the full-text indexes for each table or indexed view scripted.
/// </summary>
public bool? ScriptFullTextIndexes { get; set; } = false;
public bool? ScriptFullTextIndexes { get; set; } = true;
/// <summary>
/// Script the indexes (including XML and clustered indexes) for each table or indexed view scripted.
@@ -245,7 +245,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// <summary>
/// Script the triggers for each table or view scripted
/// </summary>
public bool? ScriptTriggers { get; set; } = false;
public bool? ScriptTriggers { get; set; } = true;
/// <summary>
/// Script the unique keys for each table or view scripted.

View File

@@ -18,6 +18,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// Gets or sets whether scripting to a single file or file per object.
/// </summary>
public string ScriptDestination { get; set; }
/// <summary>
/// Gets or sets connection string of the target database the scripting operation will run against.
/// </summary>
@@ -38,6 +43,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// </summary>
public List<ScriptingObject> ExcludeObjectCriteria { get; set; }
/// <summary>
/// Gets or sets a list of schema name of objects to script.
/// </summary>
public List<string> IncludeSchemas { get; set; }
/// <summary>
/// Gets or sets a list of schema name of objects to not script.
/// </summary>
public List<string> ExcludeSchemas { get; set; }
/// <summary>
/// Gets or sets a list of type name of objects to script.
/// </summary>
public List<string> IncludeTypes { get; set; }
/// <summary>
/// Gets or sets a list of type name of objects to not script
/// </summary>
public List<string> ExcludeTypes { get; set; }
/// <summary>
/// Gets or sets the scripting options.
/// </summary>
@@ -57,7 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
/// </summary>
public class ScriptingRequest
{
public static readonly RequestType<ScriptingParams, ScriptingResult> Type =
public static readonly RequestType<ScriptingParams, ScriptingResult> Type =
RequestType<ScriptingParams, ScriptingResult>.Create("scripting/script");
}
}

View File

@@ -89,9 +89,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
/// <param name="scriptingObject">The scripting object instance.</param>
/// <param name="database">The name of the database referenced by the Urn.</param>
/// <returns>The Urn instance.</returns>
public static Urn ToUrn(this ScriptingObject scriptingObject, string database)
public static Urn ToUrn(this ScriptingObject scriptingObject, string server, string database)
{
Validate.IsNotNull("scriptingObject", scriptingObject);
Validate.IsNotNullOrEmptyString("server", server);
Validate.IsNotNullOrWhitespaceString("database", database);
Validate.IsNotNullOrWhitespaceString("scriptingObject.Name", scriptingObject.Name);
@@ -99,7 +100,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
// Leaving the server name blank will automatically match whatever the server SMO is running against.
string urn = string.Format(
"Server/Database[@Name='{0}']/{1}[@Name='{2}' {3}]",
"Server[@Name='{0}']/Database[@Name='{1}']/{2}[@Name='{3}' {4}]",
server.ToUpper(),
database,
scriptingObject.Type,
scriptingObject.Name,

View File

@@ -52,16 +52,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
/// </summary>
/// <param name="includeCriteria">The include object criteria.</param>
/// <param name="excludeCriteria">The exclude object criteria.</param>
/// <param name="includeSchemas">The include schema filter.</param>
/// <param name="excludeSchemas">The exclude schema filter.</param>
/// <param name="includeTypes">The include type filter.</param>
/// <param name="excludeTypes">The exclude type filter.</param>
/// <param name="candidates">The candidate object to filter.</param>
/// <returns>The matching scripting objects.</returns>
public static IEnumerable<ScriptingObject> Match(
ScriptingObject includeCriteria,
ScriptingObject excludeCriteria,
string includeSchemas,
string excludeSchemas,
string includeTypes,
string excludeTypes,
IEnumerable<ScriptingObject> candidates)
{
return Match(
includeCriteria == null ? new ScriptingObject[0] : new[] { includeCriteria },
excludeCriteria == null ? new ScriptingObject[0] : new[] { excludeCriteria },
includeSchemas == null ? new List<string>(): new List<string> { includeSchemas },
excludeSchemas == null ? new List<string>(): new List<string> { excludeSchemas },
includeTypes == null ? new List<string>(): new List<string> { includeTypes },
excludeTypes == null ? new List<string>(): new List<string> { excludeTypes },
candidates);
}
@@ -71,15 +83,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
/// </summary>
/// <param name="includeCriteria">The collection of include object criteria items.</param>
/// <param name="excludeCriteria">The collection of exclude object criteria items.</param>
/// <param name="includeSchemas">The collection of include schema items.</param>
/// <param name="excludeSchemas">The collection of exclude schema items.</param>
/// <param name="includeTypes">The collection of include type items.</param>
/// <param name="excludeTypes">The collection of exclude type items.</param>
/// <param name="candidates">The candidate object to filter.</param>
/// <returns>The matching scripting objects.</returns>
public static IEnumerable<ScriptingObject> Match(
IEnumerable<ScriptingObject> includeCriteria,
IEnumerable<ScriptingObject> excludeCriteria,
IEnumerable<string> includeSchemas,
IEnumerable<string> excludeSchemas,
IEnumerable<string> includeTypes,
IEnumerable<string> excludeTypes,
IEnumerable<ScriptingObject> candidates)
{
Validate.IsNotNull("candidates", candidates);
IEnumerable<ScriptingObject> matchedObjects = new List<ScriptingObject>();
if (includeCriteria != null && includeCriteria.Any())
@@ -95,15 +115,84 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
matchedObjects = candidates;
}
if (excludeCriteria != null)
if (excludeCriteria != null && excludeCriteria.Any())
{
foreach (ScriptingObject scriptingObjectCriteria in excludeCriteria)
{
IEnumerable<ScriptingObject> matches = MatchCriteria(scriptingObjectCriteria, candidates);
IEnumerable<ScriptingObject> matches = MatchCriteria(scriptingObjectCriteria, matchedObjects);
matchedObjects = matchedObjects.Except(matches);
}
}
// Apply additional filters if included.
matchedObjects = ExcludeSchemaAndOrType(excludeSchemas, excludeTypes, matchedObjects);
matchedObjects = IncludeSchemaAndOrType(includeSchemas, includeTypes, matchedObjects);
return matchedObjects;
}
private static IEnumerable<ScriptingObject> ExcludeSchemaAndOrType(IEnumerable<string> excludeSchemas, IEnumerable<string> excludeTypes,
IEnumerable<ScriptingObject> candidates)
{
// Given a list of candidates, we remove any objects that match the excluded schema and/or type.
IEnumerable<ScriptingObject> remainingObjects = candidates;
IEnumerable<ScriptingObject> matches = null;
if (excludeSchemas != null && excludeSchemas.Any())
{
foreach (string exclude_schema in excludeSchemas)
{
matches = MatchCriteria(exclude_schema, (candidate) => { return candidate.Schema; }, candidates);
remainingObjects = remainingObjects.Except(matches);
}
}
if (excludeTypes != null && excludeTypes.Any())
{
foreach (string exclude_type in excludeTypes)
{
matches = remainingObjects.Where(o => string.Equals(exclude_type, o.Type, StringComparison.OrdinalIgnoreCase));
remainingObjects = remainingObjects.Except(matches);
}
}
return remainingObjects;
}
private static IEnumerable<ScriptingObject> IncludeSchemaAndOrType(IEnumerable<string> includeSchemas, IEnumerable<string> includeTypes,
IEnumerable<ScriptingObject> candidates)
{
// Given a list of candidates, we return a new list of scripting objects that match
// the schema and/or type filter.
IEnumerable<ScriptingObject> matchedSchema = new List<ScriptingObject>();
IEnumerable<ScriptingObject> matchedType = new List<ScriptingObject>();
IEnumerable<ScriptingObject> matchedObjects = new List<ScriptingObject>();
IEnumerable<ScriptingObject> matches = null;
if (includeSchemas != null && includeSchemas.Any())
{
foreach (string include_schema in includeSchemas)
{
matches = MatchCriteria(include_schema, (candidate) => { return candidate.Schema; }, candidates);
matchedSchema = matchedSchema.Union(matches);
}
matchedObjects = matchedSchema;
}
else
{
matchedObjects = candidates;
}
if (includeTypes != null && includeTypes.Any())
{
foreach (string include_type in includeTypes)
{
matches = matchedObjects.Where(o => string.Equals(include_type, o.Type, StringComparison.OrdinalIgnoreCase));
matchedType = matchedType.Union(matches);
}
matchedObjects = matchedType;
}
return matchedObjects;
}
@@ -137,9 +226,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
}
if (property.EndsWith(Wildcard, StringComparison.OrdinalIgnoreCase))
{
matchedObjects = candidates.Where(o => propertySelector(o).StartsWith(
propertySelector(o).Substring(0, propertySelector(o).Length - 1),
StringComparison.OrdinalIgnoreCase));
matchedObjects = candidates.Where(
o =>
propertySelector(o) != null &&
propertySelector(o).StartsWith(
propertySelector(o).Substring(0, propertySelector(o).Length - 1),
StringComparison.OrdinalIgnoreCase));
}
else
{

View File

@@ -72,11 +72,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
publishModel.ScriptProgress += this.OnPublishModelScriptProgress;
publishModel.ScriptError += this.OnPublishModelScriptError;
ScriptDestination destination = !string.IsNullOrWhiteSpace(this.Parameters.ScriptDestination)
? (ScriptDestination)Enum.Parse(typeof(ScriptDestination), this.Parameters.ScriptDestination)
: ScriptDestination.ToSingleFile;
// SMO is currently hardcoded to produce UTF-8 encoding when running on dotnet core.
ScriptOutputOptions outputOptions = new ScriptOutputOptions
{
SaveFileMode = ScriptFileMode.Overwrite,
SaveFileType = ScriptFileType.Unicode, // UTF-16
SaveFileName = this.Parameters.FilePath,
ScriptDestination = destination,
};
this.CancellationToken.ThrowIfCancellationRequested();
@@ -160,57 +165,61 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
{
SqlScriptPublishModel publishModel = new SqlScriptPublishModel(this.Parameters.ConnectionString);
// In the getter for SqlScriptPublishModel.AdvancedOptions, there is some strange logic which will
// cause the SqlScriptPublishModel.AdvancedOptions to get reset and lose all values based the ordering
// of when SqlScriptPublishModel.ScriptAllObjects is set. To workaround this, we initialize with
// SqlScriptPublishModel.ScriptAllObjects to true. If we need to set SqlScriptPublishModel.ScriptAllObjects
// to false, it must the last thing we do after setting all SqlScriptPublishModel.AdvancedOptions values.
// If we call the SqlScriptPublishModel.AdvancedOptions getter afterwards, all options will be reset.
//
publishModel.ScriptAllObjects = true;
PopulateAdvancedScriptOptions(this.Parameters.ScriptOptions, publishModel.AdvancedOptions);
// See if any filtering criteria was specified. If not, we're scripting the entire database. Otherwise, the filtering
// criteria should include the target objects to script.
//
bool hasIncludeCriteria = this.Parameters.IncludeObjectCriteria != null && this.Parameters.IncludeObjectCriteria.Any();
bool hasExcludeCriteria = this.Parameters.ExcludeObjectCriteria != null && this.Parameters.ExcludeObjectCriteria.Any();
bool hasObjectsSpecified = this.Parameters.ScriptingObjects != null && this.Parameters.ScriptingObjects.Any();
bool scriptAllObjects = !(hasIncludeCriteria || hasExcludeCriteria || hasObjectsSpecified);
bool hasCriteriaSpecified =
(this.Parameters.IncludeObjectCriteria != null && this.Parameters.IncludeObjectCriteria.Any()) ||
(this.Parameters.ExcludeObjectCriteria != null && this.Parameters.ExcludeObjectCriteria.Any()) ||
(this.Parameters.IncludeSchemas != null && this.Parameters.IncludeSchemas.Any()) ||
(this.Parameters.ExcludeSchemas != null && this.Parameters.ExcludeSchemas.Any()) ||
(this.Parameters.IncludeTypes != null && this.Parameters.IncludeTypes.Any()) ||
(this.Parameters.ExcludeTypes != null && this.Parameters.ExcludeTypes.Any());
bool scriptAllObjects = !hasObjectsSpecified && !hasCriteriaSpecified;
// In the getter for SqlScriptPublishModel.AdvancedOptions, there is some strange logic which will
// cause the SqlScriptPublishModel.AdvancedOptions to get reset and lose all values based the ordering
// of when SqlScriptPublishModel.ScriptAllObjects is set.
//
publishModel.ScriptAllObjects = scriptAllObjects;
if (scriptAllObjects)
{
Logger.Write(LogLevel.Verbose, "ScriptAllObjects is True");
// Due to the getter logic within publishModel.AdvancedOptions, we explicitly populate the options
// after we determine what objects we are scripting.
//
PopulateAdvancedScriptOptions(this.Parameters.ScriptOptions, publishModel.AdvancedOptions);
return publishModel;
}
// After setting this property, SqlScriptPublishModel.AdvancedOptions should NOT be referenced again
// or all SqlScriptPublishModel.AdvancedOptions will be reset.
//
publishModel.ScriptAllObjects = false;
Logger.Write(LogLevel.Verbose, "ScriptAllObjects is False");
// An object selection criteria was specified, so now we need to resolve the SMO Urn instances to script.
//
IEnumerable<ScriptingObject> selectedObjects = new List<ScriptingObject>();
if (hasIncludeCriteria || hasExcludeCriteria)
if (hasCriteriaSpecified)
{
// This is an expensive remote call to load all objects from the database.
//
List<ScriptingObject> allObjects = publishModel.GetDatabaseObjects();
selectedObjects = ScriptingObjectMatcher.Match(
this.Parameters.IncludeObjectCriteria,
this.Parameters.ExcludeObjectCriteria,
this.Parameters.IncludeSchemas,
this.Parameters.ExcludeSchemas,
this.Parameters.IncludeTypes,
this.Parameters.ExcludeTypes,
allObjects);
}
// If specific objects are specified, include them.
//
if (hasObjectsSpecified)
{
selectedObjects = selectedObjects.Union(this.Parameters.ScriptingObjects);
}
// Populating advanced options after we select our objects in question, otherwise we lose all
// advanced options. After this call to PopulateAdvancedScriptOptions, DO NOT reference the
// publishModel.AdvancedOptions getter as it will reset the options in the model.
//
PopulateAdvancedScriptOptions(this.Parameters.ScriptOptions, publishModel.AdvancedOptions);
Logger.Write(
LogLevel.Normal,
string.Format(
@@ -218,15 +227,49 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
selectedObjects.Count(),
string.Join(", ", selectedObjects)));
string server = GetServerNameFromLiveInstance(this.Parameters.ConnectionString);
string database = new SqlConnectionStringBuilder(this.Parameters.ConnectionString).InitialCatalog;
foreach (ScriptingObject scriptingObject in selectedObjects)
{
publishModel.SelectedObjects.Add(scriptingObject.ToUrn(database));
publishModel.SelectedObjects.Add(scriptingObject.ToUrn(server, database));
}
return publishModel;
}
private string GetServerNameFromLiveInstance(string connectionString)
{
string serverName = null;
using(SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
connection.Open();
try
{
cmd.CommandText = "select @@servername";
serverName = (string)cmd.ExecuteScalar();
}
catch (SqlException e)
{
//
// Azure SQL Data Warehouse does not support @@servername, so fallback to SERVERPROPERTY.
//
Logger.Write(
LogLevel.Verbose,
string.Format("Exception running query 'SELECT @@servername' {0}, fallback to SERVERPROPERTY query", e));
cmd.CommandText = "select SERVERPROPERTY('ServerName') AS ServerName";
serverName = (string)cmd.ExecuteScalar();
}
}
Logger.Write(LogLevel.Verbose, string.Format("Resolved server name '{0}'", serverName));
return serverName;
}
private static void PopulateAdvancedScriptOptions(ScriptOptions scriptOptionsParameters, SqlScriptOptions advancedOptions)
{
if (scriptOptionsParameters == null)