mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// 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.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters to generate a SQL model
|
||||||
|
/// </summary>
|
||||||
|
public class GenerateTSqlModelParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// URI of the project file this model is for
|
||||||
|
/// </summary>
|
||||||
|
public string ProjectUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of Sql Server to target
|
||||||
|
/// </summary>
|
||||||
|
public string ModelTargetVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Sql script file paths.
|
||||||
|
/// </summary>
|
||||||
|
public string[] FilePaths { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the generate sql model request
|
||||||
|
/// </summary>
|
||||||
|
class GenerateTSqlModelRequest
|
||||||
|
{
|
||||||
|
public static readonly RequestType<GenerateTSqlModelParams, ResultStatus> Type =
|
||||||
|
RequestType<GenerateTSqlModelParams, ResultStatus>.Create("dacFx/generateTSqlModel");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||||
|
using Microsoft.SqlServer.Dac.Model;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
using DacTableDesigner = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner.TableDesigner;
|
using DacTableDesigner = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner.TableDesigner;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.DacFx
|
namespace Microsoft.SqlTools.ServiceLayer.DacFx
|
||||||
@@ -25,6 +27,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
|
|||||||
private static readonly Lazy<DacFxService> instance = new Lazy<DacFxService>(() => new DacFxService());
|
private static readonly Lazy<DacFxService> instance = new Lazy<DacFxService>(() => new DacFxService());
|
||||||
private readonly Lazy<ConcurrentDictionary<string, DacFxOperation>> operations =
|
private readonly Lazy<ConcurrentDictionary<string, DacFxOperation>> operations =
|
||||||
new Lazy<ConcurrentDictionary<string, DacFxOperation>>(() => new ConcurrentDictionary<string, DacFxOperation>());
|
new Lazy<ConcurrentDictionary<string, DacFxOperation>>(() => new ConcurrentDictionary<string, DacFxOperation>());
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ConcurrentDictionary{String, TSqlModel}"/> that maps project uri to model
|
||||||
|
/// </summary>
|
||||||
|
public Lazy<ConcurrentDictionary<string, TSqlModel>> projectModels =
|
||||||
|
new Lazy<ConcurrentDictionary<string, TSqlModel>>(() => new ConcurrentDictionary<string, TSqlModel>());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the singleton instance object
|
/// Gets the singleton instance object
|
||||||
@@ -50,6 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
|
|||||||
serviceHost.SetRequestHandler(ValidateStreamingJobRequest.Type, this.HandleValidateStreamingJobRequest);
|
serviceHost.SetRequestHandler(ValidateStreamingJobRequest.Type, this.HandleValidateStreamingJobRequest);
|
||||||
serviceHost.SetRequestHandler(GetDefaultPublishOptionsRequest.Type, this.HandleGetDefaultPublishOptionsRequest);
|
serviceHost.SetRequestHandler(GetDefaultPublishOptionsRequest.Type, this.HandleGetDefaultPublishOptionsRequest);
|
||||||
serviceHost.SetRequestHandler(ParseTSqlScriptRequest.Type, this.HandleParseTSqlScriptRequest);
|
serviceHost.SetRequestHandler(ParseTSqlScriptRequest.Type, this.HandleParseTSqlScriptRequest);
|
||||||
|
serviceHost.SetRequestHandler(GenerateTSqlModelRequest.Type, this.HandleGenerateTSqlModelRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -323,6 +331,26 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task HandleGenerateTSqlModelRequest(GenerateTSqlModelParams requestParams, RequestContext<ResultStatus> requestContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GenerateTSqlModelOperation operation = new GenerateTSqlModelOperation(requestParams);
|
||||||
|
TSqlModel model = operation.GenerateTSqlModel();
|
||||||
|
|
||||||
|
projectModels.Value[operation.Parameters.ProjectUri] = model;
|
||||||
|
await requestContext.SendResult(new ResultStatus
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
ErrorMessage = null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteOperation(DacFxOperation operation, DacFxParams parameters, string taskName, RequestContext<DacFxResult> requestContext)
|
private void ExecuteOperation(DacFxOperation operation, DacFxParams parameters, string taskName, RequestContext<DacFxResult> requestContext)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// 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 Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
|
||||||
|
using Microsoft.SqlTools.Utility;
|
||||||
|
using Microsoft.SqlServer.Dac.Model;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.DacFx
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class to represent creating a dacfx model
|
||||||
|
/// </summary>
|
||||||
|
class GenerateTSqlModelOperation
|
||||||
|
{
|
||||||
|
public GenerateTSqlModelParams Parameters { get; }
|
||||||
|
|
||||||
|
public GenerateTSqlModelOperation(GenerateTSqlModelParams parameters)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull("parameters", parameters);
|
||||||
|
this.Parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate model from sql files, if no sql files are passed in then it generates an empty model.
|
||||||
|
/// </summary>
|
||||||
|
public TSqlModel GenerateTSqlModel()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TSqlModelOptions options = new TSqlModelOptions();
|
||||||
|
SqlServerVersion version = (SqlServerVersion)Enum.Parse(typeof(SqlServerVersion), Parameters.ModelTargetVersion);
|
||||||
|
|
||||||
|
var model = new TSqlModel(version, options);
|
||||||
|
// read all sql files
|
||||||
|
foreach (string filePath in Parameters.FilePaths)
|
||||||
|
{
|
||||||
|
string fileContent = System.IO.File.ReadAllText(filePath);
|
||||||
|
model.AddOrUpdateObjects(fileContent, filePath, null);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Write(TraceEventType.Information, $"Failed to generate model. Error: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.SqlServer.Dac;
|
using Microsoft.SqlServer.Dac;
|
||||||
@@ -16,6 +17,8 @@ using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
||||||
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
using Microsoft.SqlServer.Dac.Model;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -25,6 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
|
|||||||
public class DacFxServiceTests
|
public class DacFxServiceTests
|
||||||
{
|
{
|
||||||
private string publishProfileFolder = Path.Combine("..", "..", "..", "DacFx", "PublishProfiles");
|
private string publishProfileFolder = Path.Combine("..", "..", "..", "DacFx", "PublishProfiles");
|
||||||
|
private string TSqlModelTestFolder = Path.Combine("..", "..", "..", "DacFx", "TSqlModels");
|
||||||
private const string SourceScript = @"CREATE TABLE [dbo].[table1]
|
private const string SourceScript = @"CREATE TABLE [dbo].[table1]
|
||||||
(
|
(
|
||||||
[ID] INT NOT NULL PRIMARY KEY,
|
[ID] INT NOT NULL PRIMARY KEY,
|
||||||
@@ -687,7 +691,7 @@ FROM MissingEdgeHubInputStream'";
|
|||||||
DatabaseName = targetDb.DatabaseName,
|
DatabaseName = targetDb.DatabaseName,
|
||||||
DeploymentOptions = new DeploymentOptions()
|
DeploymentOptions = new DeploymentOptions()
|
||||||
{
|
{
|
||||||
ExcludeObjectTypes = new DeploymentOptionProperty<ObjectType[]>( new[] { ObjectType.Views })
|
ExcludeObjectTypes = new DeploymentOptionProperty<ObjectType[]>(new[] { ObjectType.Views })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -926,5 +930,102 @@ Streaming query statement contains a reference to missing output stream 'Missing
|
|||||||
Assert.That(actualValue, Is.EqualTo(expectedValue), $"Actual Property from Service is not equal to default property for {optionRow.Key}");
|
Assert.That(actualValue, Is.EqualTo(expectedValue), $"Actual Property from Service is not equal to default property for {optionRow.Key}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify the generate Tsql model operation
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void GenerateTSqlModelFromSqlFiles()
|
||||||
|
{
|
||||||
|
DacFxService service = new DacFxService();
|
||||||
|
Directory.CreateDirectory(TSqlModelTestFolder);
|
||||||
|
string sqlTable1DefinitionPath = Path.Join(TSqlModelTestFolder, "table1.sql");
|
||||||
|
string sqlTable2DefinitionPath = Path.Join(TSqlModelTestFolder, "table2.sql");
|
||||||
|
const string table1 = @"CREATE TABLE [dbo].[table1]
|
||||||
|
(
|
||||||
|
[ID] INT NOT NULL PRIMARY KEY,
|
||||||
|
)";
|
||||||
|
const string table2 = @"CREATE TABLE [dbo].[table2]
|
||||||
|
(
|
||||||
|
[ID] INT NOT NULL PRIMARY KEY,
|
||||||
|
)";
|
||||||
|
// create sql file
|
||||||
|
File.WriteAllText(sqlTable1DefinitionPath, table1);
|
||||||
|
File.WriteAllText(sqlTable2DefinitionPath, table2);
|
||||||
|
|
||||||
|
var generateTSqlScriptParams = new GenerateTSqlModelParams
|
||||||
|
{
|
||||||
|
ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"),
|
||||||
|
ModelTargetVersion = "Sql160",
|
||||||
|
FilePaths = new[] { sqlTable1DefinitionPath, sqlTable2DefinitionPath }
|
||||||
|
};
|
||||||
|
|
||||||
|
GenerateTSqlModelOperation op = new GenerateTSqlModelOperation(generateTSqlScriptParams);
|
||||||
|
var model = op.GenerateTSqlModel();
|
||||||
|
var objects = model.GetObjects(DacQueryScopes.UserDefined, ModelSchema.Table).ToList();
|
||||||
|
|
||||||
|
VerifyAndCleanup(sqlTable1DefinitionPath);
|
||||||
|
VerifyAndCleanup(sqlTable2DefinitionPath);
|
||||||
|
Directory.Delete(TSqlModelTestFolder);
|
||||||
|
|
||||||
|
Assert.That(model.Version.ToString(), Is.EqualTo(generateTSqlScriptParams.ModelTargetVersion), $"Model version is not equal to {generateTSqlScriptParams.ModelTargetVersion}");
|
||||||
|
Assert.That(objects, Is.Not.Empty);
|
||||||
|
|
||||||
|
var tableNames = objects.Select(o => o.Name.ToString()).ToList();
|
||||||
|
|
||||||
|
Assert.That(tableNames.Count, Is.EqualTo(2), "Model was not populated correctly");
|
||||||
|
CollectionAssert.AreEquivalent(tableNames, new[] { "[dbo].[table1]", "[dbo].[table2]" }, "Table names do not match");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify the generate Tsql model operation, creates an empty model when files are empty
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void GenerateEmptyTSqlModel()
|
||||||
|
{
|
||||||
|
DacFxService service = new DacFxService();
|
||||||
|
Directory.CreateDirectory(TSqlModelTestFolder);
|
||||||
|
|
||||||
|
var generateTSqlScriptParams = new GenerateTSqlModelParams
|
||||||
|
{
|
||||||
|
ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"),
|
||||||
|
ModelTargetVersion = "Sql160",
|
||||||
|
FilePaths = new string[] { }
|
||||||
|
};
|
||||||
|
|
||||||
|
GenerateTSqlModelOperation op = new GenerateTSqlModelOperation(generateTSqlScriptParams);
|
||||||
|
var model = op.GenerateTSqlModel();
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
Directory.Delete(TSqlModelTestFolder);
|
||||||
|
|
||||||
|
Assert.That(model.GetObjects(DacQueryScopes.UserDefined, ModelSchema.Table).ToList().Count, Is.EqualTo(0), "Model is not empty");
|
||||||
|
Assert.That(model.Version.ToString(), Is.EqualTo(generateTSqlScriptParams.ModelTargetVersion), $"Model version is not equal to {generateTSqlScriptParams.ModelTargetVersion}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify the generate TSql Model handle
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task VerifyGenerateTSqlModelHandle()
|
||||||
|
{
|
||||||
|
DacFxService service = new DacFxService();
|
||||||
|
Directory.CreateDirectory(TSqlModelTestFolder);
|
||||||
|
|
||||||
|
var generateTSqlScriptParams = new GenerateTSqlModelParams
|
||||||
|
{
|
||||||
|
ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"),
|
||||||
|
ModelTargetVersion = "Sql160",
|
||||||
|
FilePaths = new string[] { }
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestContext = new Mock<RequestContext<ResultStatus>>();
|
||||||
|
requestContext.Setup((RequestContext<ResultStatus> x) => x.SendResult(It.Is<ResultStatus>((result) => result.Success == true))).Returns(Task.FromResult(new object()));
|
||||||
|
await service.HandleGenerateTSqlModelRequest(generateTSqlScriptParams, requestContext.Object);
|
||||||
|
|
||||||
|
Directory.Delete(TSqlModelTestFolder);
|
||||||
|
|
||||||
|
Assert.True(service.projectModels.Value.Keys.Contains(generateTSqlScriptParams.ProjectUri), "Model was not stored under project uri");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user