Schema Compare open SCMP file (#825)

* initial open scmp changes

* more open scmp changes and adding tests

* moving some common test code

* simplify endpoint info parsing

* addressing comments and moving more common test code

* addressing comments
This commit is contained in:
Kim Santiago
2019-06-13 14:24:19 -07:00
committed by GitHub
parent 3566471c6c
commit b451670222
12 changed files with 472 additions and 8 deletions

View File

@@ -21,7 +21,7 @@ using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.Metadata;
using Microsoft.SqlTools.ServiceLayer.Profiler;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.SchemaCopmare;
using Microsoft.SqlTools.ServiceLayer.SchemaCompare;
using Microsoft.SqlTools.ServiceLayer.Scripting;
using Microsoft.SqlTools.ServiceLayer.Security;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
@@ -119,7 +119,7 @@ namespace Microsoft.SqlTools.ServiceLayer
CmsService.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(CmsService.Instance);
SchemaCopmare.SchemaCompareService.Instance.InitializeService(serviceHost);
SchemaCompare.SchemaCompareService.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(SchemaCompareService.Instance);
InitializeHostedServices(serviceProvider, serviceHost);

View File

@@ -2933,6 +2933,14 @@ namespace Microsoft.SqlTools.ServiceLayer
}
}
public static string OpenScmpConnectionBasedModelParsingError
{
get
{
return Keys.GetString(Keys.OpenScmpConnectionBasedModelParsingError);
}
}
public static string ConnectionServiceListDbErrorNotConnected(string uri)
{
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
@@ -4270,6 +4278,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string SchemaCompareExcludeIncludeNodeNotFound = "SchemaCompareExcludeIncludeNodeNotFound";
public const string OpenScmpConnectionBasedModelParsingError = "OpenScmpConnectionBasedModelParsingError";
private Keys()
{ }

View File

@@ -1719,4 +1719,8 @@
<value>Failed to find the specified change in the model</value>
<comment></comment>
</data>
<data name="OpenScmpConnectionBasedModelParsingError" xml:space="preserve">
<value>Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}'</value>
<comment></comment>
</data>
</root>

View File

@@ -796,4 +796,5 @@ ExtractInvalidVersion = Invalid version '{0}' passed. Version must be in the for
############################################################################
# Schema Compare
PublishChangesTaskName = Apply schema compare changes
SchemaCompareExcludeIncludeNodeNotFound = Failed to find the specified change in the model
SchemaCompareExcludeIncludeNodeNotFound = Failed to find the specified change in the model
OpenScmpConnectionBasedModelParsingError = Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}'

View File

@@ -1996,6 +1996,11 @@
<target state="new">Failed to find the specified change in the model</target>
<note></note>
</trans-unit>
<trans-unit id="OpenScmpConnectionBasedModelParsingError">
<source>Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}'</source>
<target state="new">Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}'</target>
<note></note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -232,5 +232,20 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts
}
}
}
public DeploymentOptions(DacDeployOptions options)
{
System.Reflection.PropertyInfo[] deploymentOptionsProperties = this.GetType().GetProperties();
foreach (var deployOptionsProp in deploymentOptionsProperties)
{
var prop = options.GetType().GetProperty(deployOptionsProp.Name);
if (prop != null)
{
deployOptionsProp.SetValue(this, prop.GetValue(options));
}
}
}
}
}

View File

@@ -0,0 +1,87 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.ServiceLayer.Utility;
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts
{
public class SchemaCompareObjectId
{
/// <summary>
/// Name to create object identifier
/// </summary>
public string[] NameParts;
/// <summary>
/// sql object type
/// </summary>
public string SqlObjectType;
}
/// <summary>
/// Parameters for a schema compare open scmp file request.
/// </summary>
public class SchemaCompareOpenScmpParams
{
/// <summary>
/// filepath of scmp
/// </summary>
public string filePath { get; set; }
}
/// <summary>
/// Parameters returned from a schema compare open scmp request.
/// </summary>
public class SchemaCompareOpenScmpResult : ResultStatus
{
/// <summary>
/// Gets or sets the current source endpoint info
/// </summary>
public SchemaCompareEndpointInfo SourceEndpointInfo { get; set; }
/// <summary>
/// Gets or sets the current target endpoint info
/// </summary>
public SchemaCompareEndpointInfo TargetEndpointInfo { get; set; }
/// <summary>
/// Gets or sets the original target name. This is the initial target name, not necessarily the same as TargetEndpointInfo if they were swapped
/// The original target name is used to determine whether to use ExcludedSourceElements or ExcludedTargetElements if source and target were swapped
/// </summary>
public string OriginalTargetName { get; set; }
/// <summary>
/// Gets or sets the original target connection string. This is the initial target connection string, not necessarily the same as TargetEndpointInfo if they were swapped
/// The target connection string is necessary if the source and target are a dacpac and db with the same name
/// </summary>
public string OriginalTargetConnectionString { get; set; }
/// <summary>
/// Gets or sets the deployment options
/// </summary>
public DeploymentOptions DeploymentOptions { get; set; }
/// <summary>
/// Gets or sets the excluded source elements. This is based on the initial source, not necessarily the same as SourceEndpointInfo if they were swapped
/// </summary>
public List<SchemaCompareObjectId> ExcludedSourceElements { get; set; }
/// <summary>
/// Gets or sets the excluded target elements. This is based on the initial target, not necessarily the same as TargetEndpointInfo if they were swapped
/// </summary>
public List<SchemaCompareObjectId> ExcludedTargetElements { get; set; }
}
/// <summary>
/// Defines the Schema Compare open scmp request type
/// </summary>
class SchemaCompareOpenScmpRequest
{
public static readonly RequestType<SchemaCompareOpenScmpParams, SchemaCompareOpenScmpResult> Type =
RequestType<SchemaCompareOpenScmpParams, SchemaCompareOpenScmpResult>.Create("schemaCompare/openScmp");
}
}

View File

@@ -4,6 +4,7 @@
//
using Microsoft.SqlServer.Dac.Compare;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.ServiceLayer.Utility;
using System.Collections.Generic;
@@ -37,6 +38,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts
/// Connection uri
/// </summary>
public string OwnerUri { get; set; }
/// <summary>
/// Connection details
/// </summary>
public ConnectionDetails ConnectionDetails { get; set; }
}
/// <summary>

View File

@@ -0,0 +1,183 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlServer.Dac;
using Microsoft.SqlServer.Dac.Compare;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
{
/// <summary>
/// Schema compare load scmp operation
/// </summary>
class SchemaCompareOpenScmpOperation : ITaskOperation
{
private CancellationTokenSource cancellation = new CancellationTokenSource();
private bool disposed = false;
public SqlTask SqlTask { get; set; }
public SchemaCompareOpenScmpParams Parameters { get; set; }
public SchemaCompareOpenScmpResult Result { get; private set; }
private XDocument scmpInfo { get; set; }
public SchemaCompareOpenScmpOperation(SchemaCompareOpenScmpParams parameters)
{
Validate.IsNotNull("parameters", parameters);
this.Parameters = parameters;
}
protected CancellationToken CancellationToken { get { return this.cancellation.Token; } }
/// <summary>
/// The error occurred during operation
/// </summary>
public string ErrorMessage { get; set; }
// The schema compare public api doesn't currently take a cancellation token so the operation can't be cancelled
public void Cancel()
{
}
/// <summary>
/// Disposes the operation.
/// </summary>
public void Dispose()
{
if (!disposed)
{
this.Cancel();
disposed = true;
}
}
public void Execute(TaskExecutionMode mode)
{
if (this.CancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(this.CancellationToken);
}
try
{
SchemaComparison compare = new SchemaComparison(this.Parameters.filePath);
// load xml file because some parsing still needs to be done
this.scmpInfo = XDocument.Load(this.Parameters.filePath);
this.Result = new SchemaCompareOpenScmpResult()
{
DeploymentOptions = new DeploymentOptions(compare.Options),
Success = true,
SourceEndpointInfo = this.GetEndpointInfo(true, compare.Source),
TargetEndpointInfo = this.GetEndpointInfo(false, compare.Target),
OriginalTargetName = this.GetOriginalTargetName(),
OriginalTargetConnectionString = this.GetOriginalTargetConnectionString(),
ExcludedSourceElements = this.GetExcludedElements(compare.ExcludedSourceObjects),
ExcludedTargetElements = this.GetExcludedElements(compare.ExcludedTargetObjects)
};
}
catch (Exception e)
{
ErrorMessage = e.Message;
Logger.Write(TraceEventType.Error, string.Format("Schema compare open scmp operation failed with exception {0}", e));
throw;
}
}
private SchemaCompareEndpointInfo GetEndpointInfo(bool source, SchemaCompareEndpoint endpoint)
{
SchemaCompareEndpointInfo endpointInfo = new SchemaCompareEndpointInfo();
// if the endpoint is a dacpac we don't need to parse the xml
SchemaCompareDacpacEndpoint dacpacEndpoint = endpoint as SchemaCompareDacpacEndpoint;
if (dacpacEndpoint != null)
{
endpointInfo.EndpointType = SchemaCompareEndpointType.Dacpac;
endpointInfo.PackageFilePath = dacpacEndpoint.FilePath;
}
else
{
// need to parse xml to get connection string of database
var result = this.scmpInfo.Descendants("ConnectionBasedModelProvider");
string searchingFor = source ? "Source" : "Target";
try
{
if (result != null)
{
foreach (XElement node in result)
{
if (node.Parent.Name.ToString().Contains(searchingFor))
{
endpointInfo.ConnectionDetails = SchemaCompareService.ConnectionServiceInstance.ParseConnectionString(node.Value);
endpointInfo.ConnectionDetails.ConnectionString = node.Value;
endpointInfo.DatabaseName = endpointInfo.ConnectionDetails.DatabaseName;
endpointInfo.EndpointType = SchemaCompareEndpointType.Database;
}
}
}
}
catch (Exception e)
{
ErrorMessage = string.Format(SR.OpenScmpConnectionBasedModelParsingError, ((SchemaCompareDatabaseEndpoint)endpoint).DatabaseName,e.Message);
Logger.Write(TraceEventType.Error, string.Format("Schema compare open scmp operation failed during xml parsing with exception {0}", e.Message));
throw;
}
}
return endpointInfo;
}
private List<SchemaCompareObjectId> GetExcludedElements(IList<SchemaComparisonExcludedObjectId> excludedObjects)
{
List<SchemaCompareObjectId> excludedElements = new List<SchemaCompareObjectId>();
foreach (SchemaComparisonExcludedObjectId entry in excludedObjects)
{
excludedElements.Add(new SchemaCompareObjectId()
{
NameParts = entry.Identifier.Parts.Cast<string>().ToArray(),
SqlObjectType = entry.TypeName
});
}
return excludedElements;
}
// The original target name is used to determine whether to use ExcludedSourceElements or ExcludedTargetElements if source and target were swapped
private string GetOriginalTargetName()
{
var result = this.scmpInfo.Descendants("PropertyElementName")
.Where(x => x.Element("Name").Value == "TargetDatabaseName")
.Select(x => x.Element("Value")).FirstOrDefault();
return result != null ? result.Value : string.Empty;
}
// The original target connection string is used if comparing a dacpac and db with the same name
private string GetOriginalTargetConnectionString()
{
var result = this.scmpInfo.Descendants("PropertyElementName")
.Where(x => x.Element("Name").Value == "TargetConnectionString")
.Select(x => x.Element("Value")).FirstOrDefault();
return result != null ? result.Value : string.Empty;
}
}
}

View File

@@ -10,11 +10,10 @@ using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.SchemaCompare;
using Microsoft.SqlServer.Dac.Compare;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare
namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
{
/// <summary>
/// Main class for SchemaCompare service
@@ -27,6 +26,9 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare
private Lazy<ConcurrentDictionary<string, SchemaComparisonResult>> schemaCompareResults =
new Lazy<ConcurrentDictionary<string, SchemaComparisonResult>>(() => new ConcurrentDictionary<string, SchemaComparisonResult>());
// For testability
internal Task CurrentSchemaCompareTask;
/// <summary>
/// Gets the singleton instance object
/// </summary>
@@ -46,8 +48,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare
serviceHost.SetRequestHandler(SchemaComparePublishChangesRequest.Type, this.HandleSchemaComparePublishChangesRequest);
serviceHost.SetRequestHandler(SchemaCompareIncludeExcludeNodeRequest.Type, this.HandleSchemaCompareIncludeExcludeNodeRequest);
serviceHost.SetRequestHandler(SchemaCompareGetDefaultOptionsRequest.Type, this.HandleSchemaCompareGetDefaultOptionsRequest);
serviceHost.SetRequestHandler(SchemaCompareOpenScmpRequest.Type, this.HandleSchemaCompareOpenScmpRequest);
}
/// <summary>
/// Handles schema compare request
/// </summary>
@@ -86,7 +91,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare
Differences = operation.Differences
});
}
catch(Exception e)
catch (Exception e)
{
await requestContext.SendResult(new SchemaCompareResult()
{
@@ -185,7 +190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare
operation = new SchemaCompareIncludeExcludeNodeOperation(parameters, compareResult);
operation.Execute(parameters.TaskExecutionMode);
await requestContext.SendResult(new ResultStatus()
{
Success = true,
@@ -227,6 +232,41 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare
}
}
/// <summary>
/// Handles schema compare open SCMP request
/// </summary>
/// <returns></returns>
public async Task HandleSchemaCompareOpenScmpRequest(SchemaCompareOpenScmpParams parameters, RequestContext<SchemaCompareOpenScmpResult> requestContext)
{
try
{
CurrentSchemaCompareTask = Task.Run(async () =>
{
SchemaCompareOpenScmpOperation operation = null;
try
{
operation = new SchemaCompareOpenScmpOperation(parameters);
operation.Execute(TaskExecutionMode.Execute);
await requestContext.SendResult(operation.Result);
}
catch (Exception e)
{
await requestContext.SendResult(new SchemaCompareOpenScmpResult()
{
Success = false,
ErrorMessage = operation == null ? e.Message : operation.ErrorMessage,
});
}
});
}
catch (Exception e)
{
await requestContext.SendError(e);
}
}
private SqlTaskManager SqlTaskManagerInstance
{
get