From c51a59dadf524f9b7a2313188c2e092f0800c5f2 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Wed, 10 Jun 2020 16:00:59 -0700 Subject: [PATCH] Adding SqlCmdVars support for DacFx Deploy (#971) * Adding SqlCmdVars support for DacFx Deploy * Adding SqlCmdVars support for GenerateDeployScript * Consolidating test logic --- .../DacFx/Contracts/DeployRequest.cs | 8 +- .../Contracts/GenerateDeployScriptRequest.cs | 8 +- .../DacFx/DeployOperation.cs | 13 +- .../DacFx/GenerateDeployScriptOperation.cs | 8 + .../DacFx/DacFxserviceTests.cs | 187 ++++++++++++++---- .../TestConnectionProfileService.cs | 2 +- 6 files changed, 181 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.cs index 8ec1a529..3e684f6a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.cs @@ -2,9 +2,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System.Collections.Generic; using Microsoft.SqlTools.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.TaskServices; -using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts { @@ -17,6 +16,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts /// Gets or sets if upgrading existing database /// public bool UpgradeExisting { get; set; } + + /// + /// Gets or sets SQLCMD variables for deployment + /// + public IDictionary SqlCommandVariableValues { get; set; } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs index 3d7dbbc0..a6c122ed 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs @@ -2,9 +2,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System.Collections.Generic; using Microsoft.SqlTools.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.TaskServices; -using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts { @@ -22,6 +21,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts /// Gets or sets the filepath where to save the deployment report /// public string DeploymentReportFilePath { get; set; } + + /// + /// Gets or sets SQLCMD variables for script generation + /// + public IDictionary SqlCommandVariableValues { get; set; } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs index c5408da9..b299cf5d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs @@ -5,11 +5,7 @@ using Microsoft.SqlServer.Dac; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; -using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; -using System; -using Microsoft.Data.SqlClient; -using System.Diagnostics; namespace Microsoft.SqlTools.ServiceLayer.DacFx { @@ -30,6 +26,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx { DacPackage dacpac = DacPackage.Load(this.Parameters.PackageFilePath); DacDeployOptions options = this.GetDefaultDeployOptions(); + + if (this.Parameters.SqlCommandVariableValues != null) + { + foreach (string key in this.Parameters.SqlCommandVariableValues.Keys) + { + options.SqlCommandVariableValues[key] = this.Parameters.SqlCommandVariableValues[key]; + } + } + this.DacServices.Deploy(dacpac, this.Parameters.DatabaseName, this.Parameters.UpgradeExisting, options, this.CancellationToken); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs index b6f79c64..e8a8c2e5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs @@ -37,6 +37,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx publishOptions.CancelToken = this.CancellationToken; publishOptions.DeployOptions = this.GetDefaultDeployOptions(); + if (this.Parameters.SqlCommandVariableValues != null) + { + foreach (string key in this.Parameters.SqlCommandVariableValues.Keys) + { + publishOptions.DeployOptions.SqlCommandVariableValues[key] = this.Parameters.SqlCommandVariableValues[key]; + } + } + this.Result = this.DacServices.Script(dacpac, this.Parameters.DatabaseName, publishOptions); // tests don't create a SqlTask, so only add the script when the SqlTask isn't null diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs index 14affe59..96ad0fa9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs @@ -3,16 +3,16 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; +using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; +using Microsoft.Data.SqlClient; using Microsoft.SqlServer.Dac; -using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.DacFx; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.Test.Common; -using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx @@ -42,6 +42,18 @@ CREATE TABLE [dbo].[table3] [col1] INT NULL, )"; + private const string databaseRefVarName = "DatabaseRef"; + private const string filterValueVarName = "FilterValue"; + + private string storedProcScript = $@" +CREATE PROCEDURE [dbo].[Procedure1] + @param1 int = 0, + @param2 int +AS + SELECT * FROM [$({databaseRefVarName})].[dbo].[Table1] WHERE Type = '$({filterValueVarName})' +RETURN 0 +"; + private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects() { var result = LiveConnectionHelper.InitLiveConnectionInfo(); @@ -167,7 +179,7 @@ CREATE TABLE [dbo].[table3] public async void ExtractDBToFileTarget() { var result = GetLiveAutoCompleteTestObjects(); - SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, doNotCleanupDb:false, databaseName:null, query:SourceScript, dbNamePrefix:"DacFxExtractDBToFileTarget"); + SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, doNotCleanupDb: false, databaseName: null, query: SourceScript, dbNamePrefix: "DacFxExtractDBToFileTarget"); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); @@ -179,7 +191,7 @@ CREATE TABLE [dbo].[table3] PackageFilePath = Path.Combine(folderPath, string.Format("{0}.sql", testdb.DatabaseName)), ApplicationName = "test", ApplicationVersion = "1.0.0.0", - ExtractTarget = DacExtractTarget.File + ExtractTarget = DacExtractTarget.File }; DacFxService service = new DacFxService(); @@ -202,7 +214,7 @@ CREATE TABLE [dbo].[table3] { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, doNotCleanupDb: false, databaseName: null, query: SourceScript, dbNamePrefix: "DacFxExtractDBToFlatTarget"); - string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest","FlatExtract"); + string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest", "FlatExtract"); Directory.CreateDirectory(folderPath); try @@ -339,28 +351,17 @@ CREATE TABLE [dbo].[table3] // first extract a dacpac var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "DacFxGenerateScriptTest"); - SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateScriptTest"); - string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); - Directory.CreateDirectory(folderPath); + SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateScriptTest"); try { - var extractParams = new ExtractParams - { - DatabaseName = sourceDb.DatabaseName, - PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)), - ApplicationName = "test", - ApplicationVersion = "1.0.0.0" - }; - DacFxService service = new DacFxService(); - ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); - service.PerformOperation(extractOperation, TaskExecutionMode.Execute); + string dacpacPath = InitialExtract(service, sourceDb, result); // generate script var generateScriptParams = new GenerateDeployScriptParams { - PackageFilePath = extractParams.PackageFilePath, + PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName }; @@ -372,7 +373,7 @@ CREATE TABLE [dbo].[table3] Assert.NotEmpty(generateScriptOperation.Result.DatabaseScript); Assert.Contains("CREATE TABLE", generateScriptOperation.Result.DatabaseScript); - VerifyAndCleanup(extractParams.PackageFilePath); + VerifyAndCleanup(dacpacPath); } finally { @@ -390,29 +391,18 @@ CREATE TABLE [dbo].[table3] var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "DacFxGenerateDeployPlanTest"); SqlTestDb targetDb = null; - DacFxService service = new DacFxService(); - string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); - Directory.CreateDirectory(folderPath); try { - var extractParams = new ExtractParams - { - DatabaseName = sourceDb.DatabaseName, - PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)), - ApplicationName = "test", - ApplicationVersion = "1.0.0.0" - }; - - ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); - service.PerformOperation(extractOperation, TaskExecutionMode.Execute); + DacFxService service = new DacFxService(); + string dacpacPath = InitialExtract(service, sourceDb, result); // generate deploy plan for deploying dacpac to targetDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "DacFxGenerateDeployPlanTestTarget"); var generateDeployPlanParams = new GenerateDeployPlanParams { - PackageFilePath = extractParams.PackageFilePath, + PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, }; @@ -424,7 +414,7 @@ CREATE TABLE [dbo].[table3] Assert.Contains("Drop", report); Assert.Contains("Alter", report); - VerifyAndCleanup(extractParams.PackageFilePath); + VerifyAndCleanup(dacpacPath); } finally { @@ -436,6 +426,131 @@ CREATE TABLE [dbo].[table3] } } + // + /// Verify that SqlCmdVars are set correctly for a deploy request + /// + [Fact] + public async void DeployWithSqlCmdVariables() + { + var result = GetLiveAutoCompleteTestObjects(); + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: storedProcScript, dbNamePrefix: "DacFxDeploySqlCmdVarsTest"); + SqlTestDb targetDb = null; + + try + { + DacFxService service = new DacFxService(); + // First extract a db to have a dacpac to deploy later + string dacpacPath = InitialExtract(service, sourceDb, result); + + // Deploy the created dacpac with SqlCmdVars + var deployParams = new DeployParams + { + PackageFilePath = dacpacPath, + DatabaseName = string.Concat(sourceDb.DatabaseName, "-deployed"), + UpgradeExisting = false, + SqlCommandVariableValues = new Dictionary() + { + { databaseRefVarName, "OtherDatabase" }, + { filterValueVarName, "Employee" } + } + }; + + DeployOperation deployOperation = new DeployOperation(deployParams, result.ConnectionInfo); + service.PerformOperation(deployOperation, TaskExecutionMode.Execute); + targetDb = SqlTestDb.CreateFromExisting(deployParams.DatabaseName); + + string deployedProc; + + using (SqlConnection conn = new SqlConnection(targetDb.ConnectionString)) + { + try + { + await conn.OpenAsync(); + deployedProc = (string)ReliableConnectionHelper.ExecuteScalar(conn, "SELECT OBJECT_DEFINITION (OBJECT_ID(N'Procedure1'));"); + } + finally + { + conn.Close(); + } + } + + Assert.Contains(deployParams.SqlCommandVariableValues[databaseRefVarName], deployedProc); + Assert.Contains(deployParams.SqlCommandVariableValues[filterValueVarName], deployedProc); + + VerifyAndCleanup(dacpacPath); + } + finally + { + sourceDb.Cleanup(); + if (targetDb != null) + { + targetDb.Cleanup(); + } + } + } + + // + /// Verify that SqlCmdVars are set correctly for a generate script request + /// + [Fact] + public async void GenerateDeployScriptWithSqlCmdVariables() + { + var result = GetLiveAutoCompleteTestObjects(); + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: storedProcScript, dbNamePrefix: "DacFxGenerateScriptSqlCmdVarsTest"); + + try + { + DacFxService service = new DacFxService(); + // First extract a db to have a dacpac to generate the script for later + string dacpacPath = InitialExtract(service, sourceDb, result); + + // Generate script for deploying source dacpac to target db with SqlCmdVars + var generateScriptParams = new GenerateDeployScriptParams + { + PackageFilePath = dacpacPath, + DatabaseName = string.Concat(sourceDb.DatabaseName, "-generated"), + SqlCommandVariableValues = new Dictionary() + { + { databaseRefVarName, "OtherDatabase" }, + { filterValueVarName, "Employee" } + } + }; + + GenerateDeployScriptOperation generateScriptOperation = new GenerateDeployScriptOperation(generateScriptParams, result.ConnectionInfo); + service.PerformOperation(generateScriptOperation, TaskExecutionMode.Script); + + // Verify the SqlCmdVars were set correctly in the script + Assert.NotEmpty(generateScriptOperation.Result.DatabaseScript); + Assert.Contains($":setvar {databaseRefVarName} \"{generateScriptParams.SqlCommandVariableValues[databaseRefVarName]}\"", generateScriptOperation.Result.DatabaseScript); + Assert.Contains($":setvar {filterValueVarName} \"{generateScriptParams.SqlCommandVariableValues[filterValueVarName]}\"", generateScriptOperation.Result.DatabaseScript); + + VerifyAndCleanup(dacpacPath); + } + finally + { + sourceDb.Cleanup(); + } + } + + private string InitialExtract(DacFxService service, SqlTestDb sourceDb, LiveConnectionHelper.TestConnectionResult result) + { + string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); + Directory.CreateDirectory(folderPath); + + var extractParams = new ExtractParams + { + DatabaseName = sourceDb.DatabaseName, + PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)), + ApplicationName = "test", + ApplicationVersion = "1.0.0.0" + }; + + ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); + service.PerformOperation(extractOperation, TaskExecutionMode.Execute); + + return extractParams.PackageFilePath; + } + private void VerifyAndCleanup(string filePath) { // Verify it was created diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConnectionProfileService.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConnectionProfileService.cs index cd6f8b44..f1d7fa42 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConnectionProfileService.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConnectionProfileService.cs @@ -120,7 +120,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common ConnectionSetting settings = TestConfigPersistenceHelper.InitSetting(); if (settings == null) { - Console.WriteLine("DBTestInstance not configured. Run 'dotnet run Microsoft.SqlTools.ServiceLayer.TestEnvConfig from the command line to configure"); + Console.WriteLine("DBTestInstance not configured. Run 'dotnet run Microsoft.SqlTools.ServiceLayer.TestEnvConfig' from the command line to configure"); } if (testServers != null && settings != null)