diff --git a/Packages.props b/Packages.props index 9b8c20d4..da22294c 100644 --- a/Packages.props +++ b/Packages.props @@ -20,8 +20,8 @@ - - + + diff --git a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8056.0-alpha.nupkg b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8056.0-alpha.nupkg deleted file mode 100644 index 467c2e00..00000000 Binary files a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8056.0-alpha.nupkg and /dev/null differ diff --git a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8086.0-alpha.nupkg b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8086.0-alpha.nupkg new file mode 100644 index 00000000..d9a24430 Binary files /dev/null and b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8086.0-alpha.nupkg differ diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs new file mode 100644 index 00000000..cd6bbdd4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts +{ + public class GetCrossPlatformCompatiblityRequest + { + public static readonly RequestType Type = RequestType.Create("sqlProjects/getCrossPlatformCompatibility"); + } + + /// + /// Result containing whether the project is cross-platform compatible + /// + public class GetCrossPlatformCompatiblityResult : ResultStatus + { + /// + /// Whether the project is cross-platform compatible + /// + public bool IsCrossPlatformCompatible { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/UpdateProjectForCrossPlatform.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/UpdateProjectForCrossPlatform.cs new file mode 100644 index 00000000..2d43c692 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/UpdateProjectForCrossPlatform.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 UpdateProjectForCrossPlatformRequest + { + public static readonly RequestType Type = RequestType.Create("sqlProjects/updateProjectForCrossPlatform"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs index 0d8b212f..091fb90b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs @@ -44,6 +44,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects serviceHost.SetRequestHandler(OpenSqlProjectRequest.Type, HandleOpenSqlProjectRequest, isParallelProcessingSupported: true); serviceHost.SetRequestHandler(CloseSqlProjectRequest.Type, HandleCloseSqlProjectRequest, isParallelProcessingSupported: true); serviceHost.SetRequestHandler(NewSqlProjectRequest.Type, HandleNewSqlProjectRequest, isParallelProcessingSupported: true); + serviceHost.SetRequestHandler(GetCrossPlatformCompatiblityRequest.Type, HandleGetCrossPlatformCompatibilityRequest, isParallelProcessingSupported: true); + serviceHost.SetRequestHandler(UpdateProjectForCrossPlatformRequest.Type, HandleUpdateProjectForCrossPlatformRequest, isParallelProcessingSupported: false); // SQL object script functions serviceHost.SetRequestHandler(AddSqlObjectScriptRequest.Type, HandleAddSqlObjectScriptRequest, isParallelProcessingSupported: false); @@ -78,12 +80,29 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects { await RunWithErrorHandling(async () => { - await SqlProject.CreateProjectAsync(requestParams.ProjectUri, requestParams.SqlProjectType, requestParams.DatabaseSchemaProvider); + await SqlProject.CreateProjectAsync(requestParams.ProjectUri, new CreateSqlProjectParams() { ProjectType = requestParams.SqlProjectType, DspVersion = requestParams.DatabaseSchemaProvider }); GetProject(requestParams.ProjectUri); // load into the cache - }, requestContext); } + internal async Task HandleGetCrossPlatformCompatibilityRequest(SqlProjectParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => + { + return new GetCrossPlatformCompatiblityResult() + { + Success = true, + ErrorMessage = null, + IsCrossPlatformCompatible = GetProject(requestParams.ProjectUri).CrossPlatformCompatible + }; + }, requestContext); + } + + internal async Task HandleUpdateProjectForCrossPlatformRequest(SqlProjectParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).UpdateForCrossPlatform(), requestContext); + } + #endregion #region SQL object script functions @@ -119,25 +138,33 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects internal async Task HandleAddDacpacReferenceRequest(AddDacpacReferenceParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).DatabaseReferences.Add( - new DacpacReference( - requestParams.DacpacPath, - requestParams.SuppressMissingDependencies, - requestParams.DatabaseVariable, - requestParams.ServerVariable)), - requestContext); + await RunWithErrorHandling(() => + { + SqlProject project = GetProject(requestParams.ProjectUri); + + project.DatabaseReferences.Add( + new DacpacReference( + requestParams.DacpacPath, + requestParams.SuppressMissingDependencies, + project.SqlCmdVariables.Get(requestParams.DatabaseVariable), + project.SqlCmdVariables.Get(requestParams.ServerVariable))); + }, requestContext); } internal async Task HandleAddSqlProjectReferenceRequest(AddSqlProjectReferenceParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).DatabaseReferences.Add( - new SqlProjectReference( - requestParams.ProjectPath, - requestParams.ProjectGuid, - requestParams.SuppressMissingDependencies, - requestParams.DatabaseVariable, - requestParams.ServerVariable)), - requestContext); + await RunWithErrorHandling(() => + { + SqlProject project = GetProject(requestParams.ProjectUri); + + project.DatabaseReferences.Add( + new SqlProjectReference( + requestParams.ProjectPath, + requestParams.ProjectGuid, + requestParams.SuppressMissingDependencies, + project.SqlCmdVariables.Get(requestParams.DatabaseVariable), + project.SqlCmdVariables.Get(requestParams.ServerVariable))); + }, requestContext); } internal async Task HandleDeleteDatabaseReferenceRequest(DeleteDatabaseReferenceParams requestParams, RequestContext requestContext) @@ -189,26 +216,67 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects #region Helper methods + /// + /// Synchronous action with standard ResultStatus + /// + /// + /// + /// private async Task RunWithErrorHandling(Action action, RequestContext requestContext) { await RunWithErrorHandling(async () => await Task.Run(action), requestContext); } + /// + /// Asynchronous action with standard ResultStatus + /// + /// + /// + /// private async Task RunWithErrorHandling(Func action, RequestContext requestContext) { - try + await RunWithErrorHandling(async () => { await action(); - await requestContext.SendResult(new ResultStatus() + return new ResultStatus() { Success = true, ErrorMessage = null - }); + }; + }, requestContext); + } + + + /// + /// Synchronous action with custom result + /// + /// + /// + /// + /// + private async Task RunWithErrorHandling(Func action, RequestContext requestContext) where T : ResultStatus, new() + { + await RunWithErrorHandling(async () => await Task.Run(action), requestContext); + } + + /// + /// Asynchronous action with custom result + /// + /// + /// + /// + /// + private async Task RunWithErrorHandling(Func> action, RequestContext requestContext) where T : ResultStatus, new() + { + try + { + T result = await action(); + await requestContext.SendResult(result); } catch (Exception ex) { - await requestContext.SendResult(new ResultStatus() + await requestContext.SendResult(new T() { Success = false, ErrorMessage = ex.Message diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index 7eb7a671..edb2fd7d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -57,5 +57,8 @@ + + PreserveNewest + diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/Inputs/SSDTProject.sqlproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/Inputs/SSDTProject.sqlproj new file mode 100644 index 00000000..818fb5d1 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/Inputs/SSDTProject.sqlproj @@ -0,0 +1,67 @@ + + + + Debug + AnyCPU + TestProjectLegacyNetFramework + 2.0 + 4.1 + {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575} + Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider + Database + + + TestProjectLegacyNetFramework + TestProjectLegacyNetFramework + 1033, CI + BySchemaAndSchemaType + True + v4.7.2 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + $(DacPacRootPath)\Extensions\Microsoft\SQLDB\Extensions\SqlServer\150\SqlSchemas\master.dacpac + False + master + + + \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs index 18e1e89d..5a90c422 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -#nullable disable - using System; using System.IO; using System.Linq; @@ -192,7 +190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding system db reference"); SystemDatabaseReference systemDbRef = (SystemDatabaseReference)service.Projects[projectUri].DatabaseReferences.First(x => x is SystemDatabaseReference); Assert.AreEqual(SystemDatabase.MSDB, systemDbRef.SystemDb, "Referenced system DB"); - Assert.AreEqual("$(EmEssDeeBee)", systemDbRef.DatabaseVariable); + Assert.AreEqual("$(EmEssDeeBee)", systemDbRef.DatabaseVariableLiteralName); Assert.IsFalse(systemDbRef.SuppressMissingDependencies, nameof(systemDbRef.SuppressMissingDependencies)); // Validate adding a dacpac reference @@ -212,8 +210,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects Assert.AreEqual(2, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference"); DacpacReference dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.First(x => x is DacpacReference); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); - Assert.AreEqual(databaseVar.Name, dacpacRef.DatabaseVariable); - Assert.AreEqual(serverVar.Name, dacpacRef.ServerVariable); + Assert.AreEqual(databaseVar.Name, dacpacRef.DatabaseVariable.VarName); + Assert.AreEqual(serverVar.Name, dacpacRef.ServerVariable.VarName); Assert.IsFalse(dacpacRef.SuppressMissingDependencies, nameof(dacpacRef.SuppressMissingDependencies)); // Validate adding a project reference @@ -235,8 +233,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects SqlProjectReference projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.First(x => x is SqlProjectReference); Assert.AreEqual(mockReferencePath, projectRef.ProjectPath, "Referenced project"); Assert.AreEqual(TEST_GUID, projectRef.ProjectGuid, "Referenced project GUID"); - Assert.AreEqual(databaseVar.Name, projectRef.DatabaseVariable); - Assert.AreEqual(serverVar.Name, projectRef.ServerVariable); + Assert.AreEqual(databaseVar.Name, projectRef.DatabaseVariable.VarName); + Assert.AreEqual(serverVar.Name, projectRef.ServerVariable.VarName); Assert.IsFalse(projectRef.SuppressMissingDependencies, nameof(projectRef.SuppressMissingDependencies)); // Validate deleting a reference @@ -337,6 +335,46 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects requestMock.AssertSuccess(nameof(service.HandleDeleteSqlCmdVariableRequest)); Assert.AreEqual(0, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after deletion not as expected"); } + + [Test] + public async Task TestCrossPlatformUpdates() + { + string inputProjectPath = Path.Join(Path.GetDirectoryName(typeof(SqlProjectsServiceTests).Assembly.Location), "SqlProjects", "Inputs", "SSDTProject.sqlproj"); + string projectPath = Path.Join(TestContext.CurrentContext.GetTestWorkingFolder(), "SSDTProject.sqlproj"); + + Directory.CreateDirectory(Path.GetDirectoryName(projectPath)!); + File.Copy(inputProjectPath, projectPath); + SqlProjectsService service = new(); + + /// Validate that the cross-platform status can be fetched + MockRequest getRequestMock = new(); + await service.HandleGetCrossPlatformCompatibilityRequest(new SqlProjectParams() + { + ProjectUri = projectPath + }, getRequestMock.Object); + + getRequestMock.AssertSuccess(nameof(service.HandleGetCrossPlatformCompatibilityRequest)); + Assert.IsFalse(getRequestMock.Result.IsCrossPlatformCompatible, "Input file should not be cross-platform compatible before conversion"); + + // Validate that the project can be updated + MockRequest updateRequestMock = new(); + await service.HandleUpdateProjectForCrossPlatformRequest(new SqlProjectParams() + { + ProjectUri = projectPath, + }, updateRequestMock.Object); + + updateRequestMock.AssertSuccess(nameof(service.HandleUpdateProjectForCrossPlatformRequest)); + + // Validate that the cross-platform status has changed + getRequestMock = new(); + await service.HandleGetCrossPlatformCompatibilityRequest(new SqlProjectParams() + { + ProjectUri = projectPath + }, getRequestMock.Object); + + getRequestMock.AssertSuccess(nameof(service.HandleGetCrossPlatformCompatibilityRequest)); + Assert.IsTrue(((GetCrossPlatformCompatiblityResult)getRequestMock.Result).IsCrossPlatformCompatible, "Input file should be cross-platform compatible after conversion"); + } } internal static class SqlProjectsExtensions