mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-22 09:35:38 -05:00
Fix scripting data in the scripting service (#337)
A regression was introduced in the scripting service refactoring where data is no longer scripted. This commit fixes the issue, and updates the tests to catch this in the future. The issue is in the getter for SqlScriptPublishModel.AdvancedOptions, there is some strange logic which will cause the SqlScriptPublishModel.AdvancedOptions to get reset and lose all values based the ordering of when SqlScriptPublishModel.ScriptAllObjects is set. In the scripting service refactoring, we started to hit this reset of the AdvanceOptions, which would lose the option to script data, along with other options. To workaround this, we initialize with SqlScriptPublishModel.ScriptAllObjects to true, and then set all SqlScriptPublishModel.AdvancedOptions values. Then, we set SqlScriptPublishModel.ScriptAllObjects, and avoid calling the SqlScriptPublishModel.AdvancedOptions getter. Also including some misc scripting service changes: 1) Adding a sequence number field to the scripting operation events 2) Adding a error message to scripting progress events 3) Expect a null scripting option parameter 4) Correctly set the exception message and details for json-rpc events 5) More logging
This commit is contained in:
@@ -12,14 +12,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
/// </summary>
|
||||
public class ScriptingCompleteParams : ScriptingEventParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or sets the error details for an error that occurred during the scripting operation.
|
||||
/// </summary>
|
||||
public string ErrorDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the error message for an error that occurred during the scripting operation.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets a value to indicate an error occurred during the scripting operation.
|
||||
/// </summary>
|
||||
public bool HasError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets a value to indicate the scripting operation was canceled.
|
||||
/// </summary>
|
||||
public bool Canceled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets a value to indicate the scripting operation successfully completed.
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -14,5 +14,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
/// Gets or sets the operation id of the scripting operation this event is associated with.
|
||||
/// </summary>
|
||||
public string OperationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sequence number. The sequence number starts at 1, and is incremented each time a scripting event is
|
||||
/// raised for the current scripting operation.
|
||||
/// </summary>
|
||||
public int SequenceNumber { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
/// <summary>
|
||||
/// Gets or sets the list of database objects returned from the list objects operation.
|
||||
/// </summary>
|
||||
public List<ScriptingObject> DatabaseObjects { get; set; }
|
||||
public List<ScriptingObject> ScriptingObjects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the count of database object returned from the list objects operation.
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
/// Gets or sets the error details if an error occurred scripting a database object.
|
||||
/// </summary>
|
||||
public string ErrorDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the error message for an error that occurred scripting a database object.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -19,6 +19,33 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
/// </summary>
|
||||
internal static class ScriptingExtensionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the status of a scripting operation for the passed scripting event.
|
||||
/// </summary>
|
||||
/// <param name="e">The scripting event.</param>
|
||||
/// <returns>The status.</returns>
|
||||
public static string GetStatus(this ScriptEventArgs e)
|
||||
{
|
||||
Validate.IsNotNull("e", e);
|
||||
|
||||
string status = null;
|
||||
|
||||
if (e.Error != null)
|
||||
{
|
||||
status = "Error";
|
||||
}
|
||||
else if (e.Completed)
|
||||
{
|
||||
status = "Completed";
|
||||
}
|
||||
else
|
||||
{
|
||||
status = "Progress";
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of ScriptingObject instances for the passed SqlScriptPublishModel instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -70,9 +70,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
this.SendCompletionNotificationEvent(new ScriptingListObjectsCompleteParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
DatabaseObjects = databaseObjects,
|
||||
ScriptingObjects = databaseObjects,
|
||||
Count = databaseObjects.Count,
|
||||
Success = true,
|
||||
SequenceNumber = 1,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -9,16 +9,10 @@ using System.Data.SqlClient;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.SqlScriptPublish;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using static Microsoft.SqlServer.Management.SqlScriptPublish.SqlScriptOptions;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
@@ -34,6 +28,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
|
||||
private int totalScriptedObjectCount = 0;
|
||||
|
||||
private int eventSequenceNumber = 1;
|
||||
|
||||
public ScriptingScriptOperation(ScriptingParams parameters)
|
||||
{
|
||||
Validate.IsNotNull("parameters", parameters);
|
||||
@@ -92,13 +88,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
Logger.Write(
|
||||
LogLevel.Verbose,
|
||||
string.Format(
|
||||
"Sending script complete notification event with total count {0} and scripted count {1}",
|
||||
"Sending script complete notification event for operation {0}, sequence number {1} with total count {2} and scripted count {3}",
|
||||
this.OperationId,
|
||||
this.eventSequenceNumber,
|
||||
this.totalScriptedObjectCount,
|
||||
this.scriptedObjectCount));
|
||||
|
||||
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
Success = true,
|
||||
});
|
||||
}
|
||||
@@ -109,7 +106,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
Logger.Write(LogLevel.Normal, string.Format("Scripting operation {0} was canceled", this.OperationId));
|
||||
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
Canceled = true,
|
||||
});
|
||||
}
|
||||
@@ -118,7 +114,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
Logger.Write(LogLevel.Error, string.Format("Scripting operation {0} failed with exception {1}", this.OperationId, e));
|
||||
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
HasError = true,
|
||||
ErrorMessage = e.Message,
|
||||
ErrorDetails = e.ToString(),
|
||||
@@ -138,39 +133,66 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
|
||||
private void SendCompletionNotificationEvent(ScriptingCompleteParams parameters)
|
||||
{
|
||||
this.SetCommomEventProperties(parameters);
|
||||
this.CompleteNotification?.Invoke(this, parameters);
|
||||
}
|
||||
|
||||
private void SendPlanNotificationEvent(ScriptingPlanNotificationParams parameters)
|
||||
{
|
||||
this.SetCommomEventProperties(parameters);
|
||||
this.PlanNotification?.Invoke(this, parameters);
|
||||
}
|
||||
|
||||
private void SendProgressNotificationEvent(ScriptingProgressNotificationParams parameters)
|
||||
{
|
||||
this.SetCommomEventProperties(parameters);
|
||||
this.ProgressNotification?.Invoke(this, parameters);
|
||||
}
|
||||
|
||||
private void SetCommomEventProperties(ScriptingEventParams parameters)
|
||||
{
|
||||
parameters.OperationId = this.OperationId;
|
||||
parameters.SequenceNumber = this.eventSequenceNumber;
|
||||
this.eventSequenceNumber += 1;
|
||||
}
|
||||
|
||||
private SqlScriptPublishModel BuildPublishModel()
|
||||
{
|
||||
SqlScriptPublishModel publishModel = new SqlScriptPublishModel(this.Parameters.ConnectionString);
|
||||
PopulateAdvancedScriptOptions(publishModel.AdvancedOptions);
|
||||
|
||||
// In the getter for SqlScriptPublishModel.AdvancedOptions, there is some strange logic which will
|
||||
// cause the SqlScriptPublishModel.AdvancedOptions to get reset and lose all values based the ordering
|
||||
// of when SqlScriptPublishModel.ScriptAllObjects is set. To workaround this, we initialize with
|
||||
// SqlScriptPublishModel.ScriptAllObjects to true. If we need to set SqlScriptPublishModel.ScriptAllObjects
|
||||
// to false, it must the last thing we do after setting all SqlScriptPublishModel.AdvancedOptions values.
|
||||
// If we call the SqlScriptPublishModel.AdvancedOptions getter afterwards, all options will be reset.
|
||||
//
|
||||
publishModel.ScriptAllObjects = true;
|
||||
|
||||
PopulateAdvancedScriptOptions(this.Parameters.ScriptOptions, publishModel.AdvancedOptions);
|
||||
|
||||
// See if any filtering criteria was specified. If not, we're scripting the entire database. Otherwise, the filtering
|
||||
// criteria should include the target objects to script.
|
||||
//
|
||||
bool hasIncludeCriteria = this.Parameters.IncludeObjectCriteria != null && this.Parameters.IncludeObjectCriteria.Any();
|
||||
bool hasExcludeCriteria = this.Parameters.ExcludeObjectCriteria != null && this.Parameters.ExcludeObjectCriteria.Any();
|
||||
bool hasObjectsSpecified = this.Parameters.ScriptingObjects != null && this.Parameters.ScriptingObjects.Any();
|
||||
|
||||
// If no object selection criteria was specified, we're scripting the entire database
|
||||
publishModel.ScriptAllObjects = !(hasIncludeCriteria || hasExcludeCriteria || hasObjectsSpecified);
|
||||
if (publishModel.ScriptAllObjects)
|
||||
bool scriptAllObjects = !(hasIncludeCriteria || hasExcludeCriteria || hasObjectsSpecified);
|
||||
if (scriptAllObjects)
|
||||
{
|
||||
publishModel.AdvancedOptions.GenerateScriptForDependentObjects = BooleanTypeOptions.True;
|
||||
Logger.Write(LogLevel.Verbose, "ScriptAllObjects is True");
|
||||
return publishModel;
|
||||
}
|
||||
|
||||
// An object selection criteria was specified, so now we need to resolve the SMO Urn instances to script.
|
||||
IEnumerable<ScriptingObject> selectedObjects = new List<ScriptingObject>();
|
||||
// After setting this property, SqlScriptPublishModel.AdvancedOptions should NOT be referenced again
|
||||
// or all SqlScriptPublishModel.AdvancedOptions will be reset.
|
||||
//
|
||||
publishModel.ScriptAllObjects = false;
|
||||
Logger.Write(LogLevel.Verbose, "ScriptAllObjects is False");
|
||||
|
||||
// An object selection criteria was specified, so now we need to resolve the SMO Urn instances to script.
|
||||
//
|
||||
IEnumerable<ScriptingObject> selectedObjects = new List<ScriptingObject>();
|
||||
if (hasIncludeCriteria || hasExcludeCriteria)
|
||||
{
|
||||
// This is an expensive remote call to load all objects from the database.
|
||||
@@ -183,6 +205,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
}
|
||||
|
||||
// If specific objects are specified, include them.
|
||||
//
|
||||
if (hasObjectsSpecified)
|
||||
{
|
||||
selectedObjects = selectedObjects.Union(this.Parameters.ScriptingObjects);
|
||||
@@ -204,9 +227,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
return publishModel;
|
||||
}
|
||||
|
||||
private void PopulateAdvancedScriptOptions(SqlScriptOptions advancedOptions)
|
||||
private static void PopulateAdvancedScriptOptions(ScriptOptions scriptOptionsParameters, SqlScriptOptions advancedOptions)
|
||||
{
|
||||
foreach (PropertyInfo optionPropInfo in this.Parameters.ScriptOptions.GetType().GetProperties())
|
||||
if (scriptOptionsParameters == null)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "No advanced options set, the ScriptOptions object is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (PropertyInfo optionPropInfo in scriptOptionsParameters.GetType().GetProperties())
|
||||
{
|
||||
PropertyInfo advancedOptionPropInfo = advancedOptions.GetType().GetProperty(optionPropInfo.Name);
|
||||
if (advancedOptionPropInfo == null)
|
||||
@@ -215,12 +244,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
continue;
|
||||
}
|
||||
|
||||
object optionValue = optionPropInfo.GetValue(this.Parameters.ScriptOptions, index: null);
|
||||
object optionValue = optionPropInfo.GetValue(scriptOptionsParameters, index: null);
|
||||
if (optionValue == null)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.Write(LogLevel.Verbose, string.Format("Skipping ScriptOptions.{0} since value is null", optionPropInfo.Name));
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -242,9 +269,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
smoValue = Enum.Parse(advancedOptionPropInfo.PropertyType, (string)optionValue, ignoreCase: true);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Logger.Write(LogLevel.Verbose, string.Format("Setting ScriptOptions.{0} to value {1}", optionPropInfo.Name, smoValue));
|
||||
#endif
|
||||
advancedOptionPropInfo.SetValue(advancedOptions, smoValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -280,23 +305,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
Logger.Write(
|
||||
LogLevel.Verbose,
|
||||
string.Format(
|
||||
"Sending scripting error progress event, Urn={0}, OperationId={1}, Completed={2}, Error={3}",
|
||||
"Sending scripting error progress event, Urn={0}, OperationId={1}, Sequence={2}, Completed={3}, Error={4}",
|
||||
e.Urn,
|
||||
this.OperationId,
|
||||
this.eventSequenceNumber,
|
||||
e.Completed,
|
||||
e.Error));
|
||||
e?.Error?.ToString() ?? "null"));
|
||||
|
||||
// Keep scripting...it's a best effort operation.
|
||||
e.ContinueScripting = true;
|
||||
|
||||
this.SendProgressNotificationEvent(new ScriptingProgressNotificationParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
ScriptingObject = e.Urn?.ToScriptingObject(),
|
||||
Status = "Error",
|
||||
Status = e.GetStatus(),
|
||||
CompletedCount = this.scriptedObjectCount,
|
||||
TotalCount = this.totalScriptedObjectCount,
|
||||
ErrorDetails = e?.ToString(),
|
||||
ErrorMessage = e?.Error?.Message,
|
||||
ErrorDetails = e?.Error?.ToString(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -310,13 +336,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
Logger.Write(
|
||||
LogLevel.Verbose,
|
||||
string.Format(
|
||||
"Sending plan notification event with count {0}, objects: {1}",
|
||||
"Sending scripting plan notification event OperationId={0}, Sequence={1}, Count={2}, Objects: {3}",
|
||||
this.OperationId,
|
||||
this.eventSequenceNumber,
|
||||
this.totalScriptedObjectCount,
|
||||
string.Join(", ", e.Urns)));
|
||||
|
||||
this.SendPlanNotificationEvent(new ScriptingPlanNotificationParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
ScriptingObjects = scriptingObjects,
|
||||
Count = scriptingObjects.Count,
|
||||
});
|
||||
@@ -334,20 +361,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
Logger.Write(
|
||||
LogLevel.Verbose,
|
||||
string.Format(
|
||||
"Sending progress event, Urn={0}, OperationId={1}, Completed={2}, Error={3}",
|
||||
"Sending progress event, Urn={0}, OperationId={1}, Sequence={2}, Status={3}, Error={4}",
|
||||
e.Urn,
|
||||
this.OperationId,
|
||||
e.Completed,
|
||||
e.Error));
|
||||
this.eventSequenceNumber,
|
||||
e.GetStatus(),
|
||||
e?.Error?.ToString() ?? "null"));
|
||||
|
||||
this.SendProgressNotificationEvent(new ScriptingProgressNotificationParams
|
||||
{
|
||||
OperationId = this.OperationId,
|
||||
ScriptingObject = e.Urn.ToScriptingObject(),
|
||||
Status = e.Completed ? "Completed" : "Progress",
|
||||
Status = e.GetStatus(),
|
||||
CompletedCount = this.scriptedObjectCount,
|
||||
TotalCount = this.totalScriptedObjectCount,
|
||||
ErrorDetails = e?.ToString(),
|
||||
ErrorMessage = e?.Error?.Message,
|
||||
ErrorDetails = e?.Error?.ToString(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user