From e60fc1a16ceb2e8792b94f2cb4f974a4eeb0ea71 Mon Sep 17 00:00:00 2001
From: Kim Santiago <31145923+kisantia@users.noreply.github.com>
Date: Thu, 24 Oct 2019 17:11:30 -0700
Subject: [PATCH] Schema compare include/exclude changes (#881)
* send back success of include/exclude request
* update dacfx nuget package and use new api to get affected dependencies of include/exclude request
* addressing comments
* rename test
* Addressing comments
---
.../Microsoft.SqlTools.ServiceLayer.csproj | 2 +-
.../SchemaCompareIncludeExcludeNodeRequest.cs | 9 ++
.../Contracts/SchemaCompareRequest.cs | 1 +
...chemaCompareIncludeExcludeNodeOperation.cs | 42 ++++++-
.../SchemaCompare/SchemaCompareService.cs | 13 ++-
.../SchemaCompare/SchemaCompareUtils.cs | 1 +
...ManagedBatchParser.IntegrationTests.csproj | 2 +-
...Tools.ServiceLayer.IntegrationTests.csproj | 2 +-
.../SchemaCompareServiceTests.cs | 108 ++++++++++++++++++
9 files changed, 170 insertions(+), 10 deletions(-)
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
index 2db9ac95..45b6f51c 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
+++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareIncludeExcludeNodeRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareIncludeExcludeNodeRequest.cs
index ae9e8071..aa9e2c3f 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareIncludeExcludeNodeRequest.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareIncludeExcludeNodeRequest.cs
@@ -6,6 +6,7 @@
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
{
@@ -40,4 +41,12 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts
public static readonly RequestType Type =
RequestType.Create("schemaCompare/includeExcludeNode");
}
+
+ ///
+ /// Parameters returned from a schema compare include/exclude request.
+ ///
+ public class SchemaCompareIncludeExcludeResult : ResultStatus
+ {
+ public List ChangedDifferences { get; set; }
+ }
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs
index 0f1621ed..0db7e4f6 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareRequest.cs
@@ -101,6 +101,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts
public string TargetScript { get; set; }
public string SourceObjectType { get; set; }
public string TargetObjectType { get; set; }
+ public bool Included { get; set; }
}
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareIncludeExcludeNodeOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareIncludeExcludeNodeOperation.cs
index 1e244c8e..537000ef 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareIncludeExcludeNodeOperation.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareIncludeExcludeNodeOperation.cs
@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
+using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
{
@@ -38,6 +39,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
public bool Success { get; set; }
+ public List ChangedDifferences;
+
public SchemaCompareIncludeExcludeNodeOperation(SchemaCompareNodeParams parameters, SchemaComparisonResult comparisonResult)
{
Validate.IsNotNull("parameters", parameters);
@@ -46,6 +49,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
this.ComparisonResult = comparisonResult;
}
+ ///
+ /// Exclude will return false if included dependencies are found. Include will also include dependencies that need to be included.
+ /// This is the same behavior as SSDT
+ ///
+ ///
public void Execute(TaskExecutionMode mode)
{
this.CancellationToken.ThrowIfCancellationRequested();
@@ -58,7 +66,27 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
throw new InvalidOperationException(SR.SchemaCompareExcludeIncludeNodeNotFound);
}
+ // Check first if the dependencies will allow this if it's an exclude request
+ if (!this.Parameters.IncludeRequest)
+ {
+ IEnumerable dependencies = this.ComparisonResult.GetExcludeDependencies(node);
+
+ bool block = dependencies.Any(d => d.Included);
+ if (block)
+ {
+ this.Success = false;
+ return;
+ }
+ }
+
this.Success = this.Parameters.IncludeRequest ? this.ComparisonResult.Include(node) : this.ComparisonResult.Exclude(node);
+
+ // create list of affected dependencies of this request
+ if (this.Success)
+ {
+ IEnumerable dependencies = this.ComparisonResult.GetIncludeDependencies(node);
+ this.ChangedDifferences = dependencies.Select(difference => SchemaCompareUtils.CreateDiffEntry(difference, null)).ToList();
+ }
}
catch (Exception e)
{
@@ -97,10 +125,16 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
System.Reflection.PropertyInfo[] properties = diffEntry.GetType().GetProperties();
foreach (var prop in properties)
{
- result = result &&
- ((prop.GetValue(diffEntry) == null &&
- prop.GetValue(entryFromDifference) == null) ||
- prop.GetValue(diffEntry).SafeToString().Equals(prop.GetValue(entryFromDifference).SafeToString()));
+ // Don't need to check if included is the same when verifying if the difference is equal
+ if (prop.Name != "Included")
+ {
+ if(!((prop.GetValue(diffEntry) == null &&
+ prop.GetValue(entryFromDifference) == null) ||
+ prop.GetValue(diffEntry).SafeToString().Equals(prop.GetValue(entryFromDifference).SafeToString())))
+ {
+ return false;
+ }
+ }
}
return result;
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs
index 94f1687d..7df42d91 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs
@@ -245,10 +245,17 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
operation.Execute(parameters.TaskExecutionMode);
- await requestContext.SendResult(new ResultStatus()
+ // update the comparison result if the include/exclude was successful
+ if(operation.Success)
{
- Success = true,
- ErrorMessage = operation.ErrorMessage
+ schemaCompareResults.Value[parameters.OperationId] = operation.ComparisonResult;
+ }
+
+ await requestContext.SendResult(new SchemaCompareIncludeExcludeResult()
+ {
+ Success = operation.Success,
+ ErrorMessage = operation.ErrorMessage,
+ ChangedDifferences = operation.ChangedDifferences
});
}
catch (Exception e)
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs
index b39b8c1b..e1eb7fcd 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs
@@ -48,6 +48,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
diffEntry.UpdateAction = difference.UpdateAction;
diffEntry.DifferenceType = difference.DifferenceType;
diffEntry.Name = difference.Name;
+ diffEntry.Included = difference.Included;
if (difference.SourceObject != null)
{
diff --git a/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj b/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj
index e702bb54..5ea6d8fd 100644
--- a/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj
+++ b/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj
@@ -32,7 +32,7 @@
-
+
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 6d417602..de0b71e3 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj
@@ -33,7 +33,7 @@
-
+
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs
index 7d9b857c..fa323634 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs
@@ -45,6 +45,19 @@ CREATE TABLE [dbo].[table3]
[col1] INT NULL,
)";
+ private const string SourceIncludeExcludeScript = @"CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT)
+GO
+CREATE TABLE t2(c1 INT PRIMARY KEY, c2 INT)
+GO
+cREATE VIEW v1 as SELECT c1 FROM t2";
+
+ private const string TargetIncludeExcludeScript = @"CREATE TABLE t1 (c1 INT PRIMARY KEY)
+GO
+CREATE TABLE t3 (c3 INT PRIMARY KEY, c2 INT)
+GO
+CREATE VIEW v2 as SELECT t1.c1, t3.c3 FROM t1, t3
+GO";
+
private const string CreateKey = @"CREATE COLUMN MASTER KEY [CMK_Auto1]
WITH (
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
@@ -801,7 +814,102 @@ WITH VALUES
sourceDb.Cleanup();
targetDb.Cleanup();
}
+ }
+ ///
+ /// Verify the schema compare request with failing exclude request because of dependencies and that include will include dependencies
+ ///
+ [Fact]
+ public async void SchemaCompareIncludeExcludeWithDependencies()
+ {
+ var result = SchemaCompareTestUtils.GetLiveAutoCompleteTestObjects();
+ SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceIncludeExcludeScript, "SchemaCompareSource");
+ SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetIncludeExcludeScript, "SchemaCompareTarget");
+
+ try
+ {
+ string targetDacpacFilePath = SchemaCompareTestUtils.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
+ };
+
+ 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);
+
+ // try to exclude
+ DiffEntry t2Diff = SchemaCompareUtils.CreateDiffEntry(schemaCompareOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "t2").First(), null);
+ SchemaCompareNodeParams t2ExcludeParams = new SchemaCompareNodeParams()
+ {
+ OperationId = schemaCompareOperation.OperationId,
+ DiffEntry = t2Diff,
+ IncludeRequest = false,
+ TaskExecutionMode = TaskExecutionMode.Execute
+ };
+
+ SchemaCompareIncludeExcludeNodeOperation t2ExcludeOperation = new SchemaCompareIncludeExcludeNodeOperation(t2ExcludeParams, schemaCompareOperation.ComparisonResult);
+ t2ExcludeOperation.Execute(TaskExecutionMode.Execute);
+ Assert.False(t2ExcludeOperation.Success, "Excluding Table t2 should fail because view v1 depends on it");
+ Assert.True(t2ExcludeOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "t2").First().Included, "Difference Table t2 should still be included because the exclude request failed");
+
+ // exclude view first, then excluding t2 should work
+ DiffEntry v1Diff = SchemaCompareUtils.CreateDiffEntry(schemaCompareOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "v1").First(), null);
+ SchemaCompareNodeParams v1ExcludeParams = new SchemaCompareNodeParams()
+ {
+ OperationId = schemaCompareOperation.OperationId,
+ DiffEntry = v1Diff,
+ IncludeRequest = false,
+ TaskExecutionMode = TaskExecutionMode.Execute
+ };
+
+ SchemaCompareIncludeExcludeNodeOperation v1ExcludeOperation = new SchemaCompareIncludeExcludeNodeOperation(v1ExcludeParams, schemaCompareOperation.ComparisonResult);
+ v1ExcludeOperation.Execute(TaskExecutionMode.Execute);
+ Assert.True(v1ExcludeOperation.Success, "Excluding View v1 should succeed");
+ Assert.False(t2ExcludeOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "v1").First().Included, "Difference View v1 should be excluded");
+
+ // try to exclude t2 again and it should succeed this time
+ t2ExcludeOperation.Execute(TaskExecutionMode.Execute);
+ Assert.True(t2ExcludeOperation.Success, "Excluding Table t2 should succeed");
+ Assert.False(t2ExcludeOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "t2").First().Included, "Difference Table t2 should still be excluded");
+
+ // including v1 should also include t2
+ SchemaCompareNodeParams v1IncludeParams = new SchemaCompareNodeParams()
+ {
+ OperationId = schemaCompareOperation.OperationId,
+ DiffEntry = v1Diff,
+ IncludeRequest = true,
+ TaskExecutionMode = TaskExecutionMode.Execute
+ };
+
+ SchemaCompareIncludeExcludeNodeOperation v1IncludeOperation = new SchemaCompareIncludeExcludeNodeOperation(v1IncludeParams, t2ExcludeOperation.ComparisonResult);
+ v1IncludeOperation.Execute(TaskExecutionMode.Execute);
+ Assert.True(v1IncludeOperation.Success, "Including v1 should succeed");
+ Assert.True(v1IncludeOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "v1").First().Included, "Difference View v1 should be included");
+ Assert.True(v1IncludeOperation.ComparisonResult.Differences.Where(x => x.SourceObject != null && x.SourceObject.Name.Parts[1] == "t2").First().Included, "Difference Table t2 should still be included");
+ Assert.True(v1IncludeOperation.ChangedDifferences != null && v1IncludeOperation.ChangedDifferences.Count == 1, "There should be one difference");
+ Assert.True(v1IncludeOperation.ChangedDifferences.First().SourceValue[1] == "t2", "The affected difference of including v1 should be t2");
+
+ // cleanup
+ SchemaCompareTestUtils.VerifyAndCleanup(targetDacpacFilePath);
+ }
+ finally
+ {
+ sourceDb.Cleanup();
+ targetDb.Cleanup();
+ }
}
private void ValidateSchemaCompareWithExcludeIncludeResults(SchemaCompareOperation schemaCompareOperation)