diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs index aa46a8d9..bb5f2a85 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs @@ -4,6 +4,7 @@ // using System; +using System.IO; using System.Threading.Tasks; using Microsoft.SqlTools.Credentials; using Microsoft.SqlTools.Extensibility; @@ -11,12 +12,14 @@ using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Agent; +using Microsoft.SqlTools.ServiceLayer.AzureBlob; using Microsoft.SqlTools.ServiceLayer.AzureFunctions; using Microsoft.SqlTools.ServiceLayer.Cms; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; using Microsoft.SqlTools.ServiceLayer.EditData; +using Microsoft.SqlTools.ServiceLayer.ExecutionPlan; using Microsoft.SqlTools.ServiceLayer.FileBrowser; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.LanguageExtensibility; @@ -25,6 +28,9 @@ using Microsoft.SqlTools.ServiceLayer.Metadata; #if INCLUDE_MIGRATION using Microsoft.SqlTools.ServiceLayer.Migration; #endif +using Microsoft.SqlTools.ServiceLayer.ModelManagement; +using Microsoft.SqlTools.ServiceLayer.NotebookConvert; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement; using Microsoft.SqlTools.ServiceLayer.Profiler; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.SchemaCompare; @@ -33,15 +39,9 @@ using Microsoft.SqlTools.ServiceLayer.Security; using Microsoft.SqlTools.ServiceLayer.ServerConfigurations; using Microsoft.SqlTools.ServiceLayer.SqlAssessment; using Microsoft.SqlTools.ServiceLayer.SqlContext; -using Microsoft.SqlTools.ServiceLayer.Workspace; -using Microsoft.SqlTools.ServiceLayer.NotebookConvert; -using Microsoft.SqlTools.ServiceLayer.ModelManagement; -using Microsoft.SqlTools.ServiceLayer.TableDesigner; -using Microsoft.SqlTools.ServiceLayer.AzureBlob; -using Microsoft.SqlTools.ServiceLayer.ExecutionPlan; -using Microsoft.SqlTools.ServiceLayer.ObjectManagement; -using System.IO; using Microsoft.SqlTools.ServiceLayer.SqlProjects; +using Microsoft.SqlTools.ServiceLayer.TableDesigner; +using Microsoft.SqlTools.ServiceLayer.Workspace; namespace Microsoft.SqlTools.ServiceLayer { diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/AddSqlCmdVariable.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/AddSqlCmdVariable.cs new file mode 100644 index 00000000..5230948e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/AddSqlCmdVariable.cs @@ -0,0 +1,36 @@ +// +// 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.SqlProjects.Contracts +{ + /// + /// Parameters for adding a SQLCMD variable to a project + /// + public class AddSqlCmdVariableParams : SqlProjectParams + { + /// + /// Name of the SQLCMD variable + /// + public string Name { get; set; } + + /// + /// Default value of the SQLCMD variable + /// + public string DefaultValue { get; set; } + + /// + /// Value of the SQLCMD variable, with or without the $() + /// + public string Value { get; set; } + } + + public class AddSqlCmdVariableRequest + { + public static readonly RequestType Type = RequestType.Create("sqlprojects/addSqlCmdVariable"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs new file mode 100644 index 00000000..21c86e0e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs @@ -0,0 +1,26 @@ +// +// 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.SqlProjects.Contracts +{ + /// + /// Parameters for deleting a SQLCMD variable from a project + /// + public class DeleteSqlCmdVariableParams : SqlProjectParams + { + /// + /// Name of the SQLCMD variable to be deleted + /// + public string Name { get; set; } + } + + public class DeleteSqlCmdVariableRequest + { + public static readonly RequestType Type = RequestType.Create("sqlprojects/deleteSqlCmdVariable"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs new file mode 100644 index 00000000..10a6f59a --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs @@ -0,0 +1,15 @@ +// +// 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.SqlProjects.Contracts +{ + public class UpdateSqlCmdVariableRequest + { + public static readonly RequestType Type = RequestType.Create("sqlprojects/updateSqlCmdVariable"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs index 4e6b661c..d55f6cc6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs @@ -43,10 +43,15 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects serviceHost.SetRequestHandler(CloseSqlProjectRequest.Type, HandleCloseSqlProjectRequest, isParallelProcessingSupported: true); serviceHost.SetRequestHandler(NewSqlProjectRequest.Type, HandleNewSqlProjectRequest, isParallelProcessingSupported: true); - // SQL object script calls + // SQL object script functions serviceHost.SetRequestHandler(AddSqlObjectScriptRequest.Type, HandleAddSqlObjectScriptRequest, isParallelProcessingSupported: false); serviceHost.SetRequestHandler(DeleteSqlObjectScriptRequest.Type, HandleDeleteSqlObjectScriptRequest, isParallelProcessingSupported: false); serviceHost.SetRequestHandler(ExcludeSqlObjectScriptRequest.Type, HandleExcludeSqlObjectScriptRequest, isParallelProcessingSupported: false); + + // SQLCMD variable functions + serviceHost.SetRequestHandler(AddSqlCmdVariableRequest.Type, HandleAddSqlCmdVariableRequest, isParallelProcessingSupported: false); + serviceHost.SetRequestHandler(DeleteSqlCmdVariableRequest.Type, HandleDeleteSqlCmdVariableRequest, isParallelProcessingSupported: false); + serviceHost.SetRequestHandler(UpdateSqlCmdVariableRequest.Type, HandleUpdateSqlCmdVariableRequest, isParallelProcessingSupported: false); } #region Handlers @@ -75,7 +80,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects #endregion - #region Sql object script calls + #region SQL object script functions internal async Task HandleAddSqlObjectScriptRequest(SqlProjectScriptParams requestParams, RequestContext requestContext) { @@ -94,6 +99,30 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects #endregion + #region SQLCMD variable functions + + internal async Task HandleAddSqlCmdVariableRequest(AddSqlCmdVariableParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).SqlCmdVariables.Add(new SqlCmdVariable(requestParams.Name, requestParams.DefaultValue, requestParams.Value)), requestContext); + } + + internal async Task HandleDeleteSqlCmdVariableRequest(DeleteSqlCmdVariableParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).SqlCmdVariables.Delete(requestParams.Name), requestContext); + } + + internal async Task HandleUpdateSqlCmdVariableRequest(AddSqlCmdVariableParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => + { + SqlProject project = GetProject(requestParams.ProjectUri); + project.SqlCmdVariables.Delete(requestParams.Name); // idempotent (won't throw if doesn't exist) + project.SqlCmdVariables.Add(new SqlCmdVariable(requestParams.Name, requestParams.DefaultValue, requestParams.Value)); + }, requestContext); + } + + #endregion + #endregion #region Helper methods diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs index 53274822..d661c2e7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs @@ -4,6 +4,7 @@ // using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.SqlServer.Dac.Projects; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; @@ -105,7 +106,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects // Validate adding a SQL object script MockRequest requestMock = new(); - string scriptRelativePath = "MyTable.sql"; + string scriptRelativePath = "MyTable.sql"; string scriptFullPath = Path.Join(Path.GetDirectoryName(projectUri), scriptRelativePath); await File.WriteAllTextAsync(scriptFullPath, "CREATE TABLE [MyTable] ([Id] INT)"); Assert.IsTrue(File.Exists(scriptFullPath), $"{scriptFullPath} expected to be on disk"); @@ -155,6 +156,61 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects Assert.AreEqual(0, service.Projects[projectUri].SqlObjectScripts.Count, "SqlObjectScripts count after delete"); Assert.IsFalse(File.Exists(scriptFullPath), $"{scriptFullPath} expected to have been deleted from disk"); } + + [Test] + public async Task TestSqlCmdVariablesAddDelete() + { + SqlProjectsService service = new(); + string projectUri = await service.CreateSqlProject(); + + Assert.AreEqual(0, service.Projects[projectUri].SqlCmdVariables.Count, "Baseline number of SQLCMD variables not as expected"); + + // Validate adding a SQLCMD variable + MockRequest requestMock = new(); + + const string variableName = "TestVarName"; + + await service.HandleAddSqlCmdVariableRequest(new AddSqlCmdVariableParams() + { + ProjectUri = projectUri, + Name = variableName, + DefaultValue = "$(TestVarDefaultValue)", + Value = "$(TestVarValue)" + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleAddSqlCmdVariableRequest)); + Assert.AreEqual(1, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after addition not as expected"); + Assert.IsTrue(service.Projects[projectUri].SqlCmdVariables.Contains(variableName), $"List of SQLCMD variables expected to contain {variableName}"); + + // Validate updating a SQLCMD variable + const string updatedDefaultValue = "$(UpdatedDefaultValue)"; + const string updatedValue = "$(UpdatedValue)"; + + requestMock = new(); + await service.HandleUpdateSqlCmdVariableRequest(new AddSqlCmdVariableParams() + { + ProjectUri = projectUri, + Name = variableName, + DefaultValue = updatedDefaultValue, + Value = updatedValue + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleUpdateSqlCmdVariableRequest)); + Assert.AreEqual(1, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after update not as expected"); + Assert.AreEqual(updatedDefaultValue, service.Projects[projectUri].SqlCmdVariables.First().DefaultValue, "Updated default value"); + Assert.AreEqual(updatedValue, service.Projects[projectUri].SqlCmdVariables.First().Value, "Updated value"); + + // Validate deleting a SQLCMD variable + requestMock = new(); + await service.HandleDeleteSqlCmdVariableRequest(new DeleteSqlCmdVariableParams() + { + ProjectUri = projectUri, + Name = variableName, + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleDeleteSqlCmdVariableRequest)); + Assert.AreEqual(0, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after deletion not as expected"); + } } internal static class SqlProjectsExtensions