Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schema Compare open SCMP file #825

Merged
merged 6 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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;
Expand Down Expand Up @@ -119,7 +119,7 @@ private static void InitializeRequestHandlersAndServices(ServiceHost serviceHost
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,20 @@ public DeploymentOptions()
}
}
}

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));
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// 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
{
/// <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<string> 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<string> 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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -37,6 +38,11 @@ public class SchemaCompareEndpointInfo
/// Connection uri
/// </summary>
public string OwnerUri { get; set; }

/// <summary>
/// Connection details
/// </summary>
public ConnectionDetails ConnectionDetails { get; set; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//
// 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.Text.RegularExpressions;
using System.Threading;
using System.Xml;

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 XmlDocument 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 = new XmlDocument();
this.scmpInfo.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(),
udeeshagautam marked this conversation as resolved.
Show resolved Hide resolved
OriginalTargetConnectionString = this.GetOriginalTargetConnectionString(),
ExcludedSourceElements = this.GetExcludedElements(true),
ExcludedTargetElements = this.GetExcludedElements(false)
};
}
catch (Exception e)
{
ErrorMessage = e.Message;
Logger.Write(TraceEventType.Error, string.Format("Schema compare load scmp operation failed with exception {0}", e.Message));
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pensivebrian should we make the connection string getter to be public in DacFx SchemaCompareDatabaseEndpoint - so that we don't have to do parsing here? What do you think? I am worried that parsing might need constant maintenance if things around this change in DacFx.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough context to have an opinion. I'll leave it up to you two.

XmlNodeList connectionBasedModelProviderNodes = this.scmpInfo.DocumentElement.SelectNodes("descendant::ConnectionBasedModelProvider");
kisantia marked this conversation as resolved.
Show resolved Hide resolved
string searchingFor = source ? "Source" : "Target";

foreach (XmlNode node in connectionBasedModelProviderNodes)
{
kisantia marked this conversation as resolved.
Show resolved Hide resolved
if (node.ParentNode.Name.Contains(searchingFor))
{
endpointInfo.ConnectionDetails = SchemaCompareService.ConnectionServiceInstance.ParseConnectionString(node.InnerText);
endpointInfo.ConnectionDetails.ConnectionString = node.InnerText;
endpointInfo.DatabaseName = endpointInfo.ConnectionDetails.DatabaseName;
endpointInfo.EndpointType = SchemaCompareEndpointType.Database;
}
}
}

return endpointInfo;
}

private List<string> GetExcludedElements(bool source)
{
XmlNodeList excludedNodes = source ? this.scmpInfo.DocumentElement.SelectNodes("descendant::ExcludedSourceElements/SelectedItem") : this.scmpInfo.DocumentElement.SelectNodes("descendant::ExcludedTargetElements/SelectedItem");
List<string> excludedElements = new List<string>();

foreach (XmlNode node in excludedNodes)
{
excludedElements.Add(GetConcatenatedElementName(node));
}

return excludedElements;
}

private string GetConcatenatedElementName(XmlNode node)
{
List<string> results = new List<string>();
foreach (XmlNode n in node.ChildNodes)
{
results.Add(n.InnerText);
}

return string.Join(".", results);
}

// The original target name is used to determine whether to use ExcludedSourceElements or ExcludedTargetElements if source and target were swapped
private string GetOriginalTargetName()
{
XmlNode node = this.scmpInfo.DocumentElement.SelectSingleNode("//PropertyElementName[Name='TargetDatabaseName']");
return node != null ? node.LastChild.InnerText : string.Empty;
}

// The original target connection string is used if comparing a dacpac and db with the same name
private string GetOriginalTargetConnectionString()
{
XmlNode node = this.scmpInfo.DocumentElement.SelectSingleNode("//PropertyElementName[Name='TargetConnectionString']");
return node != null ? node.LastChild.InnerText : string.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
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
Expand Down Expand Up @@ -46,8 +45,11 @@ public void InitializeService(ServiceHost serviceHost)
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>
Expand Down Expand Up @@ -86,7 +88,7 @@ await requestContext.SendResult(new SchemaCompareResult()
Differences = operation.Differences
});
}
catch(Exception e)
catch (Exception e)
{
await requestContext.SendResult(new SchemaCompareResult()
{
Expand Down Expand Up @@ -185,7 +187,7 @@ public async Task HandleSchemaCompareIncludeExcludeNodeRequest(SchemaCompareNode
operation = new SchemaCompareIncludeExcludeNodeOperation(parameters, compareResult);

operation.Execute(parameters.TaskExecutionMode);

await requestContext.SendResult(new ResultStatus()
{
Success = true,
Expand Down Expand Up @@ -227,6 +229,41 @@ await requestContext.SendResult(new SchemaCompareOptionsResult()
}
}

/// <summary>
/// Handles schema compare open SCMP request
/// </summary>
/// <returns></returns>
public async Task HandleSchemaCompareOpenScmpRequest(SchemaCompareOpenScmpParams parameters, RequestContext<SchemaCompareOpenScmpResult> requestContext)
{
try
{
Task schemaCompareTask = Task.Run(async () =>
kisantia marked this conversation as resolved.
Show resolved Hide resolved
{
SchemaCompareOpenScmpOperation operation = null;

try
{
operation = new SchemaCompareOpenScmpOperation(parameters);
operation.Execute(TaskExecutionMode.Execute);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make async methods with the operation is sync? Shouldn't this be async?

Copy link
Contributor

@udeeshagautam udeeshagautam Jun 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Brian I think I can answer this one... so from what I understood (from query editor calls) is that sync Handle*request methods keep the process channel between ADS and sqltoolsservice busy - so for any operation that can be take longer we create an async task/thread and start it but don't wat on it and resturn from Request*Handle method. The task then returns through RequestContext whenever it completes/errors out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. Blocking the json/rpc channel seems like a bad idea as no other request can be serviced, right? I guess the question is why makes this method async in the first place? But if this is existing pattern that's used elsewhere, then I guess it makes sense to be consistent.


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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using System.IO;
using System.Threading.Tasks;
using Xunit;
using Microsoft.SqlTools.ServiceLayer.SchemaCopmare;

namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SchemaCompare
{
Expand Down
Loading