From 11dd29d8a0842a964eab302f788949e437ffc559 Mon Sep 17 00:00:00 2001
From: Barbara Valdez <34872381+barbaravaldez@users.noreply.github.com>
Date: Fri, 29 Jul 2022 12:04:16 -0700
Subject: [PATCH] Add create model request (#1603)
* add create model request
---
.../Contracts/GenerateTSqlModelRequest.cs | 39 +++++++
.../DacFx/DacFxService.cs | 28 +++++
.../DacFx/GenerateTSqlModelOperation.cs | 53 +++++++++
.../DacFx/DacFxServiceTests.cs | 103 +++++++++++++++++-
4 files changed, 222 insertions(+), 1 deletion(-)
create mode 100644 src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateTSqlModelRequest.cs
create mode 100644 src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateTSqlModelOperation.cs
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateTSqlModelRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateTSqlModelRequest.cs
new file mode 100644
index 00000000..f9d9e561
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateTSqlModelRequest.cs
@@ -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
+{
+ ///
+ /// Parameters to generate a SQL model
+ ///
+ public class GenerateTSqlModelParams
+ {
+ ///
+ /// URI of the project file this model is for
+ ///
+ public string ProjectUri { get; set; }
+
+ ///
+ /// The version of Sql Server to target
+ ///
+ public string ModelTargetVersion { get; set; }
+
+ ///
+ /// Gets or sets the Sql script file paths.
+ ///
+ public string[] FilePaths { get; set; }
+ }
+
+ ///
+ /// Defines the generate sql model request
+ ///
+ class GenerateTSqlModelRequest
+ {
+ public static readonly RequestType Type =
+ RequestType.Create("dacFx/generateTSqlModel");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
index 32784990..d3001555 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
@@ -11,6 +11,8 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using Microsoft.SqlServer.Dac.Model;
+using Microsoft.SqlTools.ServiceLayer.Utility;
using DacTableDesigner = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner.TableDesigner;
namespace Microsoft.SqlTools.ServiceLayer.DacFx
@@ -25,6 +27,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
private static readonly Lazy instance = new Lazy(() => new DacFxService());
private readonly Lazy> operations =
new Lazy>(() => new ConcurrentDictionary());
+ ///
+ /// that maps project uri to model
+ ///
+ public Lazy> projectModels =
+ new Lazy>(() => new ConcurrentDictionary());
///
/// Gets the singleton instance object
@@ -50,6 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
serviceHost.SetRequestHandler(ValidateStreamingJobRequest.Type, this.HandleValidateStreamingJobRequest);
serviceHost.SetRequestHandler(GetDefaultPublishOptionsRequest.Type, this.HandleGetDefaultPublishOptionsRequest);
serviceHost.SetRequestHandler(ParseTSqlScriptRequest.Type, this.HandleParseTSqlScriptRequest);
+ serviceHost.SetRequestHandler(GenerateTSqlModelRequest.Type, this.HandleGenerateTSqlModelRequest);
}
///
@@ -323,6 +331,26 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
}
}
+ public async Task HandleGenerateTSqlModelRequest(GenerateTSqlModelParams requestParams, RequestContext 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 requestContext)
{
Task.Run(async () =>
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateTSqlModelOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateTSqlModelOperation.cs
new file mode 100644
index 00000000..fd1e34d1
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateTSqlModelOperation.cs
@@ -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
+{
+ ///
+ /// Class to represent creating a dacfx model
+ ///
+ class GenerateTSqlModelOperation
+ {
+ public GenerateTSqlModelParams Parameters { get; }
+
+ public GenerateTSqlModelOperation(GenerateTSqlModelParams parameters)
+ {
+ Validate.IsNotNull("parameters", parameters);
+ this.Parameters = parameters;
+ }
+
+ ///
+ /// Generate model from sql files, if no sql files are passed in then it generates an empty model.
+ ///
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs
index 6f7c201e..ab5cd971 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Dac;
@@ -16,6 +17,8 @@ using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.ServiceLayer.Test.Common;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+using Microsoft.SqlServer.Dac.Model;
using NUnit.Framework;
using Moq;
using System.Reflection;
@@ -25,6 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
public class DacFxServiceTests
{
private string publishProfileFolder = Path.Combine("..", "..", "..", "DacFx", "PublishProfiles");
+ private string TSqlModelTestFolder = Path.Combine("..", "..", "..", "DacFx", "TSqlModels");
private const string SourceScript = @"CREATE TABLE [dbo].[table1]
(
[ID] INT NOT NULL PRIMARY KEY,
@@ -687,7 +691,7 @@ FROM MissingEdgeHubInputStream'";
DatabaseName = targetDb.DatabaseName,
DeploymentOptions = new DeploymentOptions()
{
- ExcludeObjectTypes = new DeploymentOptionProperty( new[] { ObjectType.Views })
+ ExcludeObjectTypes = new DeploymentOptionProperty(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}");
}
}
+
+ ///
+ /// Verify the generate Tsql model operation
+ ///
+ [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");
+ }
+
+ ///
+ /// Verify the generate Tsql model operation, creates an empty model when files are empty
+ ///
+ [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}");
+ }
+
+ ///
+ /// Verify the generate TSql Model handle
+ ///
+ [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.Setup((RequestContext x) => x.SendResult(It.Is((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");
+ }
}
}