Files
sqltoolsservice/src/Microsoft.SqlTools.ServiceLayer/SqlAssessment/GenerateScriptOperation.cs
Aleksei Guzev bcc1f2a486 Add AQL Assessment service (#946)
[SQL Assessment API](https://docs.microsoft.com/en-us/sql/sql-assessment-api/sql-assessment-api-overview) provides a mechanism to evaluate the configuration
of SQL Server for best practices. SQL Assessment API gives a list
of recommended actions to improve SQL Server performance or security.

The SQL Assessment service is used by the expected SQL Assessment
feature of Azure Data Studio. 

SqlAssessmentService forwards JSONRPC calls to SQL Assessment engine
and wraps results as a response.

`assessment/getAssessmentItems` returns a set of checks 
applicable to a given target.

`assessment/invoke` returns a set of recommendations
for improving SQL Server instance or database configurations. 

`assessment/generateScript` returns a T-SQL script for storing
an assessment result set to a SQL data table.
2020-04-24 10:52:55 +03:00

152 lines
5.1 KiB
C#

//
// 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.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment
{
/// <summary>
/// Generates a script storing SQL Assessment results to a table.
/// </summary>
internal sealed class GenerateScriptOperation : ITaskOperation, IDisposable
{
private readonly CancellationTokenSource cancellation = new CancellationTokenSource();
private bool disposed = false;
/// <summary>
/// Gets the unique id associated with this instance.
/// </summary>
public string OperationId { get; set; }
/// <summary>
/// Gets the parameters containing assessment results
/// to be stored in a data table.
/// </summary>
public GenerateScriptParams Parameters { get; }
/// <summary>
/// Gets or sets the error message text
/// if an error occurred during task execution
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// Gets or sets the sql task that's executing the operation
/// </summary>
public SqlTask SqlTask { get; set; }
public GenerateScriptOperation(GenerateScriptParams parameters)
{
Validate.IsNotNull(nameof(parameters), parameters);
Parameters = parameters;
}
/// <summary>
/// Execute a task
/// </summary>
/// <param name="mode">Task execution mode (e.g. script or execute)</param>
/// <exception cref="InvalidOperationException">
/// The method has been called twice in parallel for the same instance.
/// </exception>
public void Execute(TaskExecutionMode mode)
{
try
{
var scriptText = GenerateScript(Parameters, cancellation.Token);
if (scriptText != null)
{
SqlTask?.AddScript(SqlTaskStatus.Succeeded, scriptText);
}
}
catch (Exception e)
{
ErrorMessage = e.Message;
Logger.Write(TraceEventType.Error, string.Format(
CultureInfo.InvariantCulture,
"SQL Assessment: generate script operation failed with exception {0}",
e.Message));
throw;
}
}
public void Cancel()
{
cancellation.Cancel();
}
#region Helpers
internal static string GenerateScript(GenerateScriptParams generateScriptParams,
CancellationToken cancellationToken)
{
const string scriptPrologue =
@"IF (NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'AssessmentResult'))
BEGIN
CREATE TABLE [dbo].[AssessmentResult](
[CheckName] [nvarchar](max) NOT NULL,
[CheckId] [nvarchar](max) NOT NULL,
[RulesetName] [nvarchar](max) NOT NULL,
[RulesetVersion] [nvarchar](max) NOT NULL,
[Severity] [nvarchar](max) NOT NULL,
[Message] [nvarchar](max) NOT NULL,
[TargetPath] [nvarchar](max) NOT NULL,
[TargetType] [nvarchar](max) NOT NULL,
[HelpLink] [nvarchar](max) NOT NULL,
[Timestamp] [datetimeoffset](7) NOT NULL
)
END
GO
INSERT INTO [dbo].[AssessmentResult] ([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp])
VALUES";
var sb = new StringBuilder();
if (generateScriptParams.Items != null)
{
sb.Append(scriptPrologue);
foreach (var item in generateScriptParams.Items)
{
cancellationToken.ThrowIfCancellationRequested();
if (item.Kind == AssessmentResultItemKind.Note)
{
sb.Append(
$"\r\n('{CUtils.EscapeStringSQuote(item.DisplayName)}','{CUtils.EscapeStringSQuote(item.CheckId)}','{CUtils.EscapeStringSQuote(item.RulesetName)}','{item.RulesetVersion}','{item.Level}','{CUtils.EscapeStringSQuote(item.Message)}','{CUtils.EscapeStringSQuote(item.TargetName)}','{item.TargetType}','{CUtils.EscapeStringSQuote(item.HelpLink)}','{item.Timestamp:yyyy-MM-dd hh:mm:ss.fff zzz}'),");
}
}
sb.Length -= 1;
}
return sb.ToString();
}
#endregion
#region IDisposable
public void Dispose()
{
if (!disposed)
{
Cancel();
cancellation.Dispose();
disposed = true;
}
}
#endregion
}
}