From 01bcfd8df92096cd7199794427e0679092d93aa6 Mon Sep 17 00:00:00 2001 From: kisantia <31145923+kisantia@users.noreply.github.com> Date: Tue, 5 Mar 2019 11:43:06 -0800 Subject: [PATCH] Add Schema Compare (#777) * successfully sends list of differences to ADS * bumping dacfx nuget package version * add second schema compare try for case when first db to db comparison fails but works on second try * move schemacompare out of dacfx folders * capitalizing * more capitalizing * addressing comments --- .../HostLoader.cs | 4 + .../Microsoft.SqlTools.ServiceLayer.csproj | 2 +- .../Contracts/SchemaCompareRequest.cs | 96 +++++++ .../SchemaCompare/SchemaCompareOperation.cs | 203 +++++++++++++++ .../SchemaCompare/SchemaCompareService.cs | 104 ++++++++ .../DacFx/DacFxserviceTests.cs | 51 ++-- ...Tools.ServiceLayer.IntegrationTests.csproj | 2 +- .../SchemaCompareServiceTests.cs | 235 ++++++++++++++++++ 8 files changed, 670 insertions(+), 27 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs index 7239dc66..d7b47456 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs @@ -21,6 +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.Scripting; using Microsoft.SqlTools.ServiceLayer.Security; using Microsoft.SqlTools.ServiceLayer.SqlContext; @@ -118,6 +119,9 @@ namespace Microsoft.SqlTools.ServiceLayer CmsService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(CmsService.Instance); + SchemaCopmare.SchemaCompareService.Instance.InitializeService(serviceHost); + serviceProvider.RegisterSingleService(SchemaCompareService.Instance); + InitializeHostedServices(serviceProvider, serviceHost); serviceHost.ServiceProvider = serviceProvider; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj index 1e49d364..cb0e88d4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj +++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs new file mode 100644 index 00000000..0cd43561 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs @@ -0,0 +1,96 @@ +// +// 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.Compare; +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 enum SchemaCompareEndpointType + { + Database, + Dacpac + } + + public class SchemaCompareEndpointInfo + { + /// + /// Gets or sets the type of the endpoint + /// + public SchemaCompareEndpointType EndpointType { get; set; } + + /// + /// Gets or sets package filepath + /// + public string PackageFilePath { get; set; } + + /// + /// Gets or sets name for the database + /// + public string DatabaseName { get; set; } + + /// + /// Connection uri + /// + public string OwnerUri { get; set; } + } + + /// + /// Parameters for a schema compare request. + /// + public class SchemaCompareParams + { + /// + /// Gets or sets the source endpoint info + /// + public SchemaCompareEndpointInfo SourceEndpointInfo { get; set; } + + /// + /// Gets or sets the target endpoint info + /// + public SchemaCompareEndpointInfo TargetEndpointInfo { get; set; } + + /// + /// Executation mode for the operation. Default is execution + /// + public TaskExecutionMode TaskExecutionMode { get; set; } + } + + /// + /// Parameters returned from a schema compare request. + /// + public class SchemaCompareResult : ResultStatus + { + public string OperationId { get; set; } + + public bool AreEqual { get; set; } + + public List Differences { get; set; } + } + + public class DiffEntry + { + public SchemaUpdateAction UpdateAction; + public SchemaDifferenceType DifferenceType; + public string Name; + public string SourceValue; + public string TargetValue; + public DiffEntry Parent; + public List Children; + public string SourceScript; + public string TargetScript; + } + + /// + /// Defines the Schema Compare request type + /// + class SchemaCompareRequest + { + public static readonly RequestType Type = + RequestType.Create("schemaCompare/compare"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs new file mode 100644 index 00000000..15b37da6 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs @@ -0,0 +1,203 @@ +// +// 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.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; + +namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare +{ + /// + /// Schema compare operation + /// + class SchemaCompareOperation : ITaskOperation + { + private CancellationTokenSource cancellation = new CancellationTokenSource(); + private bool disposed = false; + + /// + /// Gets the unique id associated with this instance. + /// + public string OperationId { get; private set; } + + public SqlTask SqlTask { get; set; } + + public SchemaCompareParams Parameters { get; set; } + + public string SourceConnectionString { get; set; } + + public string TargetConnectionString { get; set; } + + public SchemaComparisonResult ComparisonResult { get; set; } + + public List Differences; + + public SchemaCompareOperation(SchemaCompareParams parameters, ConnectionInfo sourceConnInfo, ConnectionInfo targetConnInfo) + { + Validate.IsNotNull("parameters", parameters); + this.Parameters = parameters; + this.SourceConnectionString = GetConnectionString(sourceConnInfo, parameters.SourceEndpointInfo.DatabaseName); + this.TargetConnectionString = GetConnectionString(targetConnInfo, parameters.TargetEndpointInfo.DatabaseName); + this.OperationId = Guid.NewGuid().ToString(); + } + + protected CancellationToken CancellationToken { get { return this.cancellation.Token; } } + + /// + /// The error occurred during operation + /// + public string ErrorMessage { get; set; } + + /// + /// Cancel operation + /// + public void Cancel() + { + if (!this.cancellation.IsCancellationRequested) + { + Logger.Write(TraceEventType.Verbose, string.Format("Cancel invoked for OperationId {0}", this.OperationId)); + this.cancellation.Cancel(); + } + } + + /// + /// Disposes the operation. + /// + public void Dispose() + { + if (!disposed) + { + this.Cancel(); + disposed = true; + } + } + + public void Execute(TaskExecutionMode mode) + { + if (this.CancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(this.CancellationToken); + } + + try + { + SchemaCompareEndpoint sourceEndpoint = CreateSchemaCompareEndpoint(this.Parameters.SourceEndpointInfo, this.SourceConnectionString); + SchemaCompareEndpoint targetEndpoint = CreateSchemaCompareEndpoint(this.Parameters.TargetEndpointInfo, this.TargetConnectionString); + + SchemaComparison comparison = new SchemaComparison(sourceEndpoint, targetEndpoint); + this.ComparisonResult = comparison.Compare(); + + // try one more time if it didn't work the first time + if(!this.ComparisonResult.IsValid) + { + this.ComparisonResult = comparison.Compare(); + } + + this.Differences = new List(); + foreach (SchemaDifference difference in this.ComparisonResult.Differences) + { + DiffEntry diffEntry = CreateDiffEntry(difference, null); + this.Differences.Add(diffEntry); + } + } + catch (Exception e) + { + Logger.Write(TraceEventType.Error, string.Format("Schema compare operation {0} failed with exception {1}", this.OperationId, e)); + throw; + } + } + + private DiffEntry CreateDiffEntry(SchemaDifference difference, DiffEntry parent) + { + DiffEntry diffEntry = new DiffEntry(); + diffEntry.UpdateAction = difference.UpdateAction; + diffEntry.DifferenceType = difference.DifferenceType; + diffEntry.Name = difference.Name; + + if (difference.SourceObject != null) + { + diffEntry.SourceValue = GetName(difference.SourceObject.Name.ToString()); + } + if (difference.TargetObject != null) + { + diffEntry.TargetValue = GetName(difference.TargetObject.Name.ToString()); + } + + if (difference.DifferenceType == SchemaDifferenceType.Object) + { + // set source and target scripts + if (difference.SourceObject != null) + { + string sourceScript; + difference.SourceObject.TryGetScript(out sourceScript); + diffEntry.SourceScript = RemoveExcessWhitespace(sourceScript); + } + if (difference.TargetObject != null) + { + string targetScript; + difference.TargetObject.TryGetScript(out targetScript); + diffEntry.TargetScript = RemoveExcessWhitespace(targetScript); + } + } + + diffEntry.Children = new List(); + + foreach (SchemaDifference child in difference.Children) + { + diffEntry.Children.Add(CreateDiffEntry(child, diffEntry)); + } + + return diffEntry; + } + + private SchemaCompareEndpoint CreateSchemaCompareEndpoint(SchemaCompareEndpointInfo endpointInfo, string connectionString) + { + switch (endpointInfo.EndpointType) + { + case SchemaCompareEndpointType.Dacpac: + { + return new SchemaCompareDacpacEndpoint(endpointInfo.PackageFilePath); + } + case SchemaCompareEndpointType.Database: + { + return new SchemaCompareDatabaseEndpoint(connectionString); + } + default: + { + return null; + } + } + } + + private string GetConnectionString(ConnectionInfo connInfo, string databaseName) + { + if (connInfo == null) + { + return null; + } + + connInfo.ConnectionDetails.DatabaseName = databaseName; + return ConnectionService.BuildConnectionString(connInfo.ConnectionDetails); + } + + private string RemoveExcessWhitespace(string script) + { + // replace all multiple spaces with single space + return Regex.Replace(script, " {2,}", " "); + } + + private string GetName(string name) + { + // remove brackets from name + return Regex.Replace(name, @"[\[\]]", ""); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs new file mode 100644 index 00000000..d3cfa5af --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs @@ -0,0 +1,104 @@ +// +// 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; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts; +using Microsoft.SqlTools.ServiceLayer.Hosting; +using Microsoft.SqlTools.ServiceLayer.TaskServices; +using System; +using System.Collections.Concurrent; +using System.Data.SqlClient; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.SchemaCompare; + +namespace Microsoft.SqlTools.ServiceLayer.SchemaCopmare +{ + /// + /// Main class for SchemaCompare service + /// + class SchemaCompareService + { + private static ConnectionService connectionService = null; + private static readonly Lazy instance = new Lazy(() => new SchemaCompareService()); + + /// + /// 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); + } + + /// + /// 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); + + Task schemaCompareTask = Task.Run(async () => + { + try + { + SchemaCompareOperation operation = new SchemaCompareOperation(parameters, sourceConnInfo, targetConnInfo); + operation.Execute(parameters.TaskExecutionMode); + + await requestContext.SendResult(new SchemaCompareResult() + { + OperationId = operation.OperationId, + Success = true, + ErrorMessage = operation.ErrorMessage, + AreEqual = operation.ComparisonResult.IsEqual, + Differences = operation.Differences + }); + } + catch(Exception e) + { + await requestContext.SendError(e); + } + }); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + internal static ConnectionService ConnectionServiceInstance + { + get + { + if (connectionService == null) + { + connectionService = ConnectionService.Instance; + } + return connectionService; + } + set + { + connectionService = value; + } + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs index 33d5dcad..780bf3ef 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs @@ -3,14 +3,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.Test.Common; using Moq; using System; -using System.Data.SqlClient; using System.IO; using System.Threading.Tasks; using Xunit; @@ -19,6 +18,28 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx { public class DacFxServiceTests { + private const string SourceScript = @"CREATE TABLE [dbo].[table1] +( + [ID] INT NOT NULL PRIMARY KEY, + [Date] DATE NOT NULL +) +CREATE TABLE [dbo].[table2] +( + [ID] INT NOT NULL PRIMARY KEY, + [col1] NCHAR(10) NULL +)"; + + private const string TargetScript = @"CREATE TABLE [dbo].[table2] +( + [ID] INT NOT NULL PRIMARY KEY, + [col1] NCHAR(10) NULL, + [col2] NCHAR(10) NULL +) +CREATE TABLE [dbo].[table3] +( + [ID] INT NOT NULL PRIMARY KEY, + [col1] INT NULL, +)"; private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects() { @@ -263,17 +284,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx private async Task>> SendAndValidateGenerateDeployPlanRequest() { var result = GetLiveAutoCompleteTestObjects(); - SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateDeployPlanTest"); - sourceDb.RunQuery(@"CREATE TABLE [dbo].[table1] -( - [ID] INT NOT NULL PRIMARY KEY, - [Date] DATE NOT NULL -) -CREATE TABLE [dbo].[table2] -( - [ID] INT NOT NULL PRIMARY KEY, - [col1] NCHAR(10) NULL -)"); + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "DacFxGenerateDeployPlanTest"); + DacFxService service = new DacFxService(); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); @@ -293,18 +305,7 @@ CREATE TABLE [dbo].[table2] var generateDeployPlanRequestContext = new Mock>(); generateDeployPlanRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); - SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateDeployPlanTestTarget"); - targetDb.RunQuery(@"CREATE TABLE [dbo].[table2] -( - [ID] INT NOT NULL PRIMARY KEY, - [col1] NCHAR(10) NULL, - [col2] NCHAR(10) NULL -) -CREATE TABLE [dbo].[table3] -( - [ID] INT NOT NULL PRIMARY KEY, - [col1] INT NULL, -)"); + SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "DacFxGenerateDeployPlanTestTarget"); var generateDeployPlanParams = new GenerateDeployPlanParams { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index c246c75c..803dafe2 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -35,7 +35,7 @@ - + diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs new file mode 100644 index 00000000..8f3d13c6 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs @@ -0,0 +1,235 @@ +// +// 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; +using Microsoft.SqlTools.ServiceLayer.DacFx; +using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.SchemaCompare; +using Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts; +using Microsoft.SqlTools.ServiceLayer.TaskServices; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Moq; +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SchemaCompare +{ + public class SchemaCompareServiceTests + { + private const string SourceScript = @"CREATE TABLE [dbo].[table1] +( + [ID] INT NOT NULL PRIMARY KEY, + [Date] DATE NOT NULL +) +CREATE TABLE [dbo].[table2] +( + [ID] INT NOT NULL PRIMARY KEY, + [col1] NCHAR(10) NULL +)"; + + private const string TargetScript = @"CREATE TABLE [dbo].[table2] +( + [ID] INT NOT NULL PRIMARY KEY, + [col1] NCHAR(10) NULL, + [col2] NCHAR(10) NULL +) +CREATE TABLE [dbo].[table3] +( + [ID] INT NOT NULL PRIMARY KEY, + [col1] INT NULL, +)"; + + private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects() + { + var result = LiveConnectionHelper.InitLiveConnectionInfo(); + return result; + } + + private async Task>> SendAndValidateSchemaCompareRequestDacpacToDacpac() + { + var result = GetLiveAutoCompleteTestObjects(); + var schemaCompareRequestContext = new Mock>(); + schemaCompareRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + // create dacpacs from databases + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "SchemaCompareSource"); + SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "SchemaCompareTarget"); + string sourceDacpacFilePath = CreateDacpac(sourceDb); + string targetDacpacFilePath = CreateDacpac(targetDb); + + SchemaCompareEndpointInfo sourceInfo = new SchemaCompareEndpointInfo(); + SchemaCompareEndpointInfo targetInfo = new SchemaCompareEndpointInfo(); + + sourceInfo.EndpointType = SchemaCompareEndpointType.Dacpac; + sourceInfo.PackageFilePath = sourceDacpacFilePath; + targetInfo.EndpointType = SchemaCompareEndpointType.Dacpac; + targetInfo.PackageFilePath = targetDacpacFilePath; + + var schemaCompareParams = new SchemaCompareParams + { + SourceEndpointInfo = sourceInfo, + TargetEndpointInfo = targetInfo + }; + + DacFxService service = new DacFxService(); + SchemaCompareOperation schemaCompareOperation = new SchemaCompareOperation(schemaCompareParams, null, null); + schemaCompareOperation.Execute(TaskExecutionMode.Execute); + + Assert.True(schemaCompareOperation.ComparisonResult.IsValid); + Assert.False(schemaCompareOperation.ComparisonResult.IsEqual); + Assert.NotNull(schemaCompareOperation.ComparisonResult.Differences); + + // cleanup + VerifyAndCleanup(sourceDacpacFilePath); + VerifyAndCleanup(targetDacpacFilePath); + sourceDb.Cleanup(); + targetDb.Cleanup(); + + return schemaCompareRequestContext; + } + + private async Task>> SendAndValidateSchemaCompareRequestDatabaseToDatabase() + { + var result = GetLiveAutoCompleteTestObjects(); + var schemaCompareRequestContext = new Mock>(); + schemaCompareRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "SchemaCompareSource"); + SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "SchemaCompareTarget"); + string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); + Directory.CreateDirectory(folderPath); + + SchemaCompareEndpointInfo sourceInfo = new SchemaCompareEndpointInfo(); + SchemaCompareEndpointInfo targetInfo = new SchemaCompareEndpointInfo(); + + sourceInfo.EndpointType = SchemaCompareEndpointType.Database; + sourceInfo.DatabaseName = sourceDb.DatabaseName; + targetInfo.EndpointType = SchemaCompareEndpointType.Database; + targetInfo.DatabaseName = targetDb.DatabaseName; + + var schemaCompareParams = new SchemaCompareParams + { + SourceEndpointInfo = sourceInfo, + TargetEndpointInfo = targetInfo + }; + + DacFxService service = new DacFxService(); + SchemaCompareOperation schemaCompareOperation = new SchemaCompareOperation(schemaCompareParams, result.ConnectionInfo, result.ConnectionInfo); + schemaCompareOperation.Execute(TaskExecutionMode.Execute); + + Assert.True(schemaCompareOperation.ComparisonResult.IsValid); + Assert.False(schemaCompareOperation.ComparisonResult.IsEqual); + Assert.NotNull(schemaCompareOperation.ComparisonResult.Differences); + + // cleanup + sourceDb.Cleanup(); + targetDb.Cleanup(); + + return schemaCompareRequestContext; + } + + private async Task>> SendAndValidateSchemaCompareRequestDatabaseToDacpac() + { + var result = GetLiveAutoCompleteTestObjects(); + var schemaCompareRequestContext = new Mock>(); + schemaCompareRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "SchemaCompareSource"); + SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "SchemaCompareTarget"); + string targetDacpacFilePath = CreateDacpac(targetDb); + + SchemaCompareEndpointInfo sourceInfo = new SchemaCompareEndpointInfo(); + SchemaCompareEndpointInfo targetInfo = new SchemaCompareEndpointInfo(); + + sourceInfo.EndpointType = SchemaCompareEndpointType.Database; + sourceInfo.DatabaseName = sourceDb.DatabaseName; + targetInfo.EndpointType = SchemaCompareEndpointType.Dacpac; + targetInfo.PackageFilePath = targetDacpacFilePath; + + var schemaCompareParams = new SchemaCompareParams + { + SourceEndpointInfo = sourceInfo, + TargetEndpointInfo = targetInfo + }; + + DacFxService service = new DacFxService(); + SchemaCompareOperation schemaCompareOperation = new SchemaCompareOperation(schemaCompareParams, result.ConnectionInfo, null); + schemaCompareOperation.Execute(TaskExecutionMode.Execute); + + Assert.True(schemaCompareOperation.ComparisonResult.IsValid); + Assert.False(schemaCompareOperation.ComparisonResult.IsEqual); + Assert.NotNull(schemaCompareOperation.ComparisonResult.Differences); + + // cleanup + VerifyAndCleanup(targetDacpacFilePath); + sourceDb.Cleanup(); + targetDb.Cleanup(); + + return schemaCompareRequestContext; + } + + /// + /// Verify the schema compare request comparing two dacpacs + /// + [Fact] + public void SchemaCompareDacpacToDacpac() + { + Assert.NotNull(SendAndValidateSchemaCompareRequestDacpacToDacpac()); + } + + /// + /// Verify the schema compare request comparing a two databases + /// + [Fact] + public async void SchemaCompareDatabaseToDatabase() + { + Assert.NotNull(await SendAndValidateSchemaCompareRequestDatabaseToDatabase()); + } + + /// + /// Verify the schema compare request comparing a database to a dacpac + /// + [Fact] + public async void SchemaCompareDatabaseToDacpac() + { + Assert.NotNull(await SendAndValidateSchemaCompareRequestDatabaseToDacpac()); + } + + private void VerifyAndCleanup(string filePath) + { + // Verify it was created + Assert.True(File.Exists(filePath)); + + // Remove the file + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + + private string CreateDacpac(SqlTestDb testdb) + { + var result = GetLiveAutoCompleteTestObjects(); + string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); + Directory.CreateDirectory(folderPath); + + var extractParams = new ExtractParams + { + DatabaseName = testdb.DatabaseName, + PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", testdb.DatabaseName)), + ApplicationName = "test", + ApplicationVersion = new Version(1, 0) + }; + + DacFxService service = new DacFxService(); + ExtractOperation operation = new ExtractOperation(extractParams, result.ConnectionInfo); + service.PerformOperation(operation); + + return extractParams.PackageFilePath; + } + } +}