//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using Microsoft.SqlServer.Dac.Compare;
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.Threading;
using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare
{
///
/// Class to represent an in-progress schema compare include/exclude Node operation
///
class SchemaCompareIncludeExcludeNodeOperation : 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 SchemaCompareNodeParams Parameters { get; }
protected CancellationToken CancellationToken { get { return this.cancellation.Token; } }
public string ErrorMessage { get; set; }
public SqlTask SqlTask { get; set; }
public SchemaComparisonResult ComparisonResult { get; set; }
public bool Success { get; set; }
public List AffectedDependencies;
public List BlockingDependencies;
public SchemaCompareIncludeExcludeNodeOperation(SchemaCompareNodeParams parameters, SchemaComparisonResult comparisonResult)
{
Validate.IsNotNull("parameters", parameters);
this.Parameters = parameters;
Validate.IsNotNull("comparisonResult", comparisonResult);
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();
try
{
SchemaDifference node = this.FindDifference(this.ComparisonResult.Differences, this.Parameters.DiffEntry) ?? throw new InvalidOperationException(SR.SchemaCompareExcludeIncludeNodeNotFound);
this.Success = this.Parameters.IncludeRequest ? this.ComparisonResult.Include(node) : this.ComparisonResult.Exclude(node);
// if include request (pass or fail), send dependencies that might have been affected by this request, given by GetIncludeDependencies()
if(this.Parameters.IncludeRequest)
{
IEnumerable affectedDependencies = this.ComparisonResult.GetIncludeDependencies(node);
this.AffectedDependencies = affectedDependencies.Select(difference => SchemaCompareUtils.CreateDiffEntry(difference: difference, parent: null)).ToList();
}
else
{ // if exclude was successful, the possible affected dependencies are given by GetIncludedDependencies()
if(this.Success)
{
IEnumerable affectedDependencies = this.ComparisonResult.GetIncludeDependencies(node);
this.AffectedDependencies = affectedDependencies.Select(difference => SchemaCompareUtils.CreateDiffEntry(difference: difference, parent: null)).ToList();
}
// if not successful, send back the exclude dependencies that caused it to fail
else
{
IEnumerable blockingDependencies = this.ComparisonResult.GetExcludeDependencies(node);
blockingDependencies = blockingDependencies.Where(difference => difference.Included == node.Included);
this.BlockingDependencies = blockingDependencies.Select(difference => SchemaCompareUtils.CreateDiffEntry(difference: difference, parent: null)).ToList();
}
}
}
catch (Exception e)
{
ErrorMessage = e.Message;
Logger.Write(TraceEventType.Error, string.Format("Schema compare publish changes operation {0} failed with exception {1}", this.OperationId, e.Message));
throw;
}
}
private SchemaDifference FindDifference(IEnumerable differences, DiffEntry diffEntry)
{
foreach (var difference in differences)
{
if (IsEqual(difference, diffEntry))
{
return difference;
}
else
{
var childDiff = FindDifference(difference.Children, diffEntry);
if (childDiff != null)
{
return childDiff;
}
}
}
return null;
}
private bool IsEqual(SchemaDifference difference, DiffEntry diffEntry)
{
bool result = true;
// Create a diff entry from difference and check if it matches the diff entry passed
DiffEntry entryFromDifference = SchemaCompareUtils.CreateDiffEntry(difference, null);
System.Reflection.PropertyInfo[] properties = diffEntry.GetType().GetProperties();
foreach (var prop in properties)
{
// 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;
}
// The schema compare public api doesn't currently take a cancellation token so the operation can't be cancelled
public void Cancel()
{
}
///
/// Disposes the operation.
///
public void Dispose()
{
if (!disposed)
{
this.Cancel();
disposed = true;
}
}
}
}