// // 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.Concurrent; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.SqlServer.Dac.Compare; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare { /// /// Main class for SchemaCompare service /// class SchemaCompareService { private static ConnectionService connectionService = null; private SqlTaskManager sqlTaskManagerInstance = null; private static readonly Lazy instance = new Lazy(() => new SchemaCompareService()); private Lazy> schemaCompareResults = new Lazy>(() => new ConcurrentDictionary()); private Lazy> currentComparisonCancellationAction = new Lazy>(() => new ConcurrentDictionary()); // For testability internal Task CurrentSchemaCompareTask; /// /// Gets the singleton instance object /// public static SchemaCompareService Instance { get { return instance.Value; } } /// /// Initializes the service instance /// /// public void InitializeService(ServiceHost serviceHost) { serviceHost.SetRequestHandler(SchemaCompareRequest.Type, this.HandleSchemaCompareRequest); serviceHost.SetRequestHandler(SchemaCompareCancellationRequest.Type, this.HandleSchemaCompareCancelRequest); serviceHost.SetRequestHandler(SchemaCompareGenerateScriptRequest.Type, this.HandleSchemaCompareGenerateScriptRequest); serviceHost.SetRequestHandler(SchemaComparePublishDatabaseChangesRequest.Type, this.HandleSchemaComparePublishDatabaseChangesRequest); serviceHost.SetRequestHandler(SchemaComparePublishProjectChangesRequest.Type, this.HandleSchemaComparePublishProjectChangesRequest); serviceHost.SetRequestHandler(SchemaCompareIncludeExcludeNodeRequest.Type, this.HandleSchemaCompareIncludeExcludeNodeRequest); serviceHost.SetRequestHandler(SchemaCompareGetDefaultOptionsRequest.Type, this.HandleSchemaCompareGetDefaultOptionsRequest); serviceHost.SetRequestHandler(SchemaCompareOpenScmpRequest.Type, this.HandleSchemaCompareOpenScmpRequest); serviceHost.SetRequestHandler(SchemaCompareSaveScmpRequest.Type, this.HandleSchemaCompareSaveScmpRequest); } /// /// Handles schema compare request /// /// public async Task HandleSchemaCompareRequest(SchemaCompareParams parameters, RequestContext requestContext) { try { ConnectionInfo sourceConnInfo; ConnectionInfo targetConnInfo; ConnectionServiceInstance.TryFindConnection( parameters.SourceEndpointInfo.OwnerUri, out sourceConnInfo); ConnectionServiceInstance.TryFindConnection( parameters.TargetEndpointInfo.OwnerUri, out targetConnInfo); CurrentSchemaCompareTask = Task.Run(async () => { SchemaCompareOperation operation = null; try { operation = new SchemaCompareOperation(parameters, sourceConnInfo, targetConnInfo); currentComparisonCancellationAction.Value[operation.OperationId] = operation.Cancel; operation.Execute(parameters.TaskExecutionMode); // add result to dictionary of results schemaCompareResults.Value[operation.OperationId] = operation.ComparisonResult; await requestContext.SendResult(new SchemaCompareResult() { OperationId = operation.OperationId, Success = operation.ComparisonResult.IsValid, ErrorMessage = operation.ErrorMessage, AreEqual = operation.ComparisonResult.IsEqual, Differences = operation.Differences }); // clean up cancellation action now that the operation is complete (using try remove to avoid exception) Action cancelAction = null; currentComparisonCancellationAction.Value.TryRemove(operation.OperationId, out cancelAction); } catch (Exception e) { Logger.Write(TraceEventType.Error, "Failed to compare schema. Error: " + e); await requestContext.SendResult(new SchemaCompareResult() { OperationId = operation != null ? operation.OperationId : null, Success = false, ErrorMessage = operation == null ? e.Message : operation.ErrorMessage, }); } }); } catch (Exception e) { await requestContext.SendError(e); } } /// /// Handles schema compare cancel request /// /// public async Task HandleSchemaCompareCancelRequest(SchemaCompareCancelParams parameters, RequestContext requestContext) { try { Action cancelAction = null; if (currentComparisonCancellationAction.Value.TryRemove(parameters.OperationId, out cancelAction)) { if (cancelAction != null) { cancelAction.Invoke(); await requestContext.SendResult(new ResultStatus() { Success = true, ErrorMessage = null }); } } else { await requestContext.SendResult(new ResultStatus() { Success = false, ErrorMessage = SR.SchemaCompareSessionNotFound }); } } catch (Exception e) { await requestContext.SendError(e); } } /// /// Handles request for schema compare generate deploy script /// /// public async Task HandleSchemaCompareGenerateScriptRequest(SchemaCompareGenerateScriptParams parameters, RequestContext requestContext) { SchemaCompareGenerateScriptOperation operation = null; try { SchemaComparisonResult compareResult = schemaCompareResults.Value[parameters.OperationId]; operation = new SchemaCompareGenerateScriptOperation(parameters, compareResult); SqlTask sqlTask = null; TaskMetadata metadata = new TaskMetadata(); metadata.TaskOperation = operation; metadata.TaskExecutionMode = parameters.TaskExecutionMode; metadata.ServerName = parameters.TargetServerName; metadata.DatabaseName = parameters.TargetDatabaseName; metadata.Name = SR.GenerateScriptTaskName; sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata); await requestContext.SendResult(new ResultStatus() { Success = true, ErrorMessage = operation.ErrorMessage }); } catch (Exception e) { Logger.Write(TraceEventType.Error, "Failed to generate schema compare script. Error: " + e); await requestContext.SendResult(new ResultStatus() { Success = false, ErrorMessage = operation == null ? e.Message : operation.ErrorMessage, }); } } /// /// Handles request for schema compare publish database changes script /// /// public async Task HandleSchemaComparePublishDatabaseChangesRequest(SchemaComparePublishDatabaseChangesParams parameters, RequestContext requestContext) { SchemaComparePublishDatabaseChangesOperation operation = null; try { SchemaComparisonResult compareResult = schemaCompareResults.Value[parameters.OperationId]; operation = new SchemaComparePublishDatabaseChangesOperation(parameters, compareResult); SqlTask sqlTask = null; TaskMetadata metadata = new TaskMetadata { TaskOperation = operation, ServerName = parameters.TargetServerName, DatabaseName = parameters.TargetDatabaseName, Name = SR.PublishChangesTaskName }; sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata); await requestContext.SendResult(new ResultStatus() { Success = true, ErrorMessage = operation.ErrorMessage }); } catch (Exception e) { Logger.Write(TraceEventType.Error, "Failed to publish schema compare database changes. Error: " + e); await requestContext.SendResult(new ResultStatus() { Success = false, ErrorMessage = operation == null ? e.Message : operation.ErrorMessage, }); } } /// /// Handles request for schema compare publish database changes script /// /// public async Task HandleSchemaComparePublishProjectChangesRequest(SchemaComparePublishProjectChangesParams parameters, RequestContext requestContext) { SchemaComparePublishProjectChangesOperation operation = null; try { SchemaComparisonResult compareResult = schemaCompareResults.Value[parameters.OperationId]; operation = new SchemaComparePublishProjectChangesOperation(parameters, compareResult); TaskMetadata metadata = new() { TaskOperation = operation, TargetLocation = parameters.TargetProjectPath, Name = SR.PublishChangesTaskName }; SqlTask sqlTask = SqlTaskManagerInstance.CreateTask(metadata); await sqlTask.RunAsync(); await requestContext.SendResult(new SchemaComparePublishProjectResult() { ChangedFiles = operation.PublishResult.ChangedFiles, AddedFiles = operation.PublishResult.AddedFiles, DeletedFiles = operation.PublishResult.DeletedFiles, Success = true, ErrorMessage = operation.ErrorMessage }); } catch (Exception e) { Logger.Write(TraceEventType.Error, "Failed to publish schema compare database changes. Error: " + e); await requestContext.SendResult(new SchemaComparePublishProjectResult() { ChangedFiles = Array.Empty(), AddedFiles = Array.Empty(), DeletedFiles = Array.Empty(), Success = false, ErrorMessage = operation?.ErrorMessage ?? e.Message }); } } /// /// Handles request for exclude incude node in Schema compare result /// /// public async Task HandleSchemaCompareIncludeExcludeNodeRequest(SchemaCompareNodeParams parameters, RequestContext requestContext) { SchemaCompareIncludeExcludeNodeOperation operation = null; try { SchemaComparisonResult compareResult = schemaCompareResults.Value[parameters.OperationId]; operation = new SchemaCompareIncludeExcludeNodeOperation(parameters, compareResult); operation.Execute(parameters.TaskExecutionMode); // update the comparison result if the include/exclude was successful if(operation.Success) { schemaCompareResults.Value[parameters.OperationId] = operation.ComparisonResult; } await requestContext.SendResult(new SchemaCompareIncludeExcludeResult() { Success = operation.Success, ErrorMessage = operation.ErrorMessage, AffectedDependencies = operation.AffectedDependencies, BlockingDependencies = operation.BlockingDependencies }); } catch (Exception e) { Logger.Write(TraceEventType.Error, "Failed to select compare schema result node. Error: " + e); await requestContext.SendResult(new ResultStatus() { Success = false, ErrorMessage = operation == null ? e.Message : operation.ErrorMessage, }); } } /// /// Handles request to create default deployment options as per DacFx /// /// public async Task HandleSchemaCompareGetDefaultOptionsRequest(SchemaCompareGetOptionsParams parameters, RequestContext requestContext) { try { // this does not need to be an async operation since this only creates and returns the default object DeploymentOptions options = DeploymentOptions.GetDefaultSchemaCompareOptions(); await requestContext.SendResult(new SchemaCompareOptionsResult() { DefaultDeploymentOptions = options, Success = true, ErrorMessage = null }); } catch (Exception e) { await requestContext.SendResult(new SchemaCompareOptionsResult() { DefaultDeploymentOptions = null, Success = false, ErrorMessage = e.Message }); } } /// /// Handles schema compare open SCMP request /// /// public async Task HandleSchemaCompareOpenScmpRequest(SchemaCompareOpenScmpParams parameters, RequestContext 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); } } /// /// Handles schema compare save SCMP request /// /// public async Task HandleSchemaCompareSaveScmpRequest(SchemaCompareSaveScmpParams parameters, RequestContext requestContext) { try { ConnectionInfo sourceConnInfo; ConnectionInfo targetConnInfo; ConnectionServiceInstance.TryFindConnection(parameters.SourceEndpointInfo.OwnerUri, out sourceConnInfo); ConnectionServiceInstance.TryFindConnection(parameters.TargetEndpointInfo.OwnerUri, out targetConnInfo); CurrentSchemaCompareTask = Task.Run(async () => { SchemaCompareSaveScmpOperation operation = null; try { operation = new SchemaCompareSaveScmpOperation(parameters, sourceConnInfo, targetConnInfo); operation.Execute(parameters.TaskExecutionMode); await requestContext.SendResult(new ResultStatus() { Success = true, ErrorMessage = operation.ErrorMessage, }); } catch (Exception e) { Logger.Write(TraceEventType.Error, "Failed to save scmp file. Error: " + e); await requestContext.SendResult(new SchemaCompareResult() { OperationId = operation != null ? operation.OperationId : null, Success = false, ErrorMessage = operation == null ? e.Message : operation.ErrorMessage, }); } }); } catch (Exception e) { await requestContext.SendError(e); } } private SqlTaskManager SqlTaskManagerInstance { get { if (sqlTaskManagerInstance == null) { sqlTaskManagerInstance = SqlTaskManager.Instance; } return sqlTaskManagerInstance; } set { sqlTaskManagerInstance = value; } } internal static ConnectionService ConnectionServiceInstance { get { if (connectionService == null) { connectionService = ConnectionService.Instance; } return connectionService; } set { connectionService = value; } } } }