diff --git a/Packages.props b/Packages.props index 5912fb2f..d662de1e 100644 --- a/Packages.props +++ b/Packages.props @@ -21,7 +21,7 @@ - + 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 deleted file mode 100644 index d9a24430..00000000 Binary files a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8086.0-alpha.nupkg and /dev/null differ diff --git a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8093.0-alpha.nupkg b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8093.0-alpha.nupkg new file mode 100644 index 00000000..9fef7734 Binary files /dev/null and b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8093.0-alpha.nupkg differ diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDacpacReferenceParams.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDacpacReferenceParams.cs index 47712942..12acbcc5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDacpacReferenceParams.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDacpacReferenceParams.cs @@ -13,18 +13,12 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts /// /// Parameters for adding a Dacpac reference to a SQL project /// - public class AddDacpacReferenceParams : AddDatabaseReferenceParams + public class AddDacpacReferenceParams : AddUserDatabaseReferenceParams { /// /// Path to the .dacpac file /// public string DacpacPath { get; set; } - - /// - /// SQLCMD variable name for specifying the other server this reference is to, if different from that of the current project. - /// If this is set, DatabaseVariable must also be set. - /// - public string? ServerVariable { get; set; } } public class AddDacpacReferenceRequest diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDatabaseReferenceParams.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDatabaseReferenceParams.cs index ddad37a0..1a6a18d5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDatabaseReferenceParams.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddDatabaseReferenceParams.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -#nullable disable - namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts { /// @@ -18,8 +16,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts public bool SuppressMissingDependencies { get; set; } /// - /// SQLCMD variable name for specifying the other database this reference is to, if different from that of the current project + /// Literal name used to reference another database in the same server, if not using SQLCMD variables /// - public string? DatabaseVariable { get; set; } + public string? DatabaseLiteral { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSqlProjectReferenceParams.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSqlProjectReferenceParams.cs index 1e96aa5c..25343441 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSqlProjectReferenceParams.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSqlProjectReferenceParams.cs @@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts /// /// Parameters for adding a reference to another SQL project /// - public class AddSqlProjectReferenceParams : AddDatabaseReferenceParams + public class AddSqlProjectReferenceParams : AddUserDatabaseReferenceParams { /// /// Path to the referenced .sqlproj file @@ -23,13 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts /// /// GUID for the referenced SQL project /// - public string? ProjectGuid { get; set; } - - /// - /// SQLCMD variable name for specifying the other server this reference is to, if different from that of the current project. - /// If this is set, DatabaseVariable must also be set. - /// - public string? ServerVariable { get; set; } + public string ProjectGuid { get; set; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSystemDatabaseReference.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSystemDatabaseReference.cs index 83b1fc7e..95951cf5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSystemDatabaseReference.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddSystemDatabaseReference.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -#nullable disable - using Microsoft.SqlServer.Dac.Projects; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddUserDatabaseReferenceParams.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddUserDatabaseReferenceParams.cs new file mode 100644 index 00000000..17420ac7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/DatabaseReferences/AddUserDatabaseReferenceParams.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 System; + +namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts +{ + public abstract class AddUserDatabaseReferenceParams : AddDatabaseReferenceParams + { + /// + /// SQLCMD variable name for specifying the other database this reference is to, if different from that of the current project + /// + public string? DatabaseVariable { get; set; } + + /// + /// SQLCMD variable name for specifying the other server this reference is to, if different from that of the current project. + /// If this is set, DatabaseVariable must also be set. + /// + public string? ServerVariable { get; set; } + + /// + /// Throws if either both DatabaseVariable and DatabaseLiteral are set. This only validates + /// what is necessary for Tools Service. The DacFx Projects library does comprehensive validation. + /// + /// + internal void Validate() + { + if (DatabaseVariable != null && DatabaseLiteral != null) + { + throw new ArgumentException($"Both {nameof(DatabaseVariable)} and {nameof(DatabaseLiteral)} cannot be set."); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs index 8b5de800..b591e690 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs @@ -3,6 +3,8 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/CloseSqlProject.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/CloseSqlProject.cs index a00688ca..f6b3d7d1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/CloseSqlProject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/CloseSqlProject.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs index cd6bbdd4..8c221450 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetCrossPlatformCompatibility.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/NewSqlProject.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/NewSqlProject.cs index 7a01457c..5bb93594 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/NewSqlProject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/NewSqlProject.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -#nullable disable - using Microsoft.SqlServer.Dac.Projects; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/OpenSqlProject.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/OpenSqlProject.cs index 8d7088f0..3d0af8e5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/OpenSqlProject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/OpenSqlProject.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs index 492e8b33..84331c30 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/DeleteSqlCmdVariable.cs @@ -3,8 +3,6 @@ // 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; @@ -18,7 +16,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts /// /// Name of the SQLCMD variable to be deleted /// - public string Name { get; set; } + public string? Name { get; set; } } public class DeleteSqlCmdVariableRequest diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs index 504a9918..05bae35a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlCmdVariables/UpdateSqlCmdvariable.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/AddSqlObjectScript.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/AddSqlObjectScript.cs index 2396ba93..54d828ac 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/AddSqlObjectScript.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/AddSqlObjectScript.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/DeleteSqlObjectScript.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/DeleteSqlObjectScript.cs index 2615ec79..fce63055 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/DeleteSqlObjectScript.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/DeleteSqlObjectScript.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/ExcludeSqlObjectScript.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/ExcludeSqlObjectScript.cs index b48aaa13..2405e7a2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/ExcludeSqlObjectScript.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/SqlObjectScripts/ExcludeSqlObjectScript.cs @@ -3,8 +3,6 @@ // 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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs index 666984de..9663f92f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs @@ -81,20 +81,20 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects internal async Task HandleOpenSqlProjectRequest(SqlProjectParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri), requestContext); + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!), requestContext); } internal async Task HandleCloseSqlProjectRequest(SqlProjectParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => Projects.TryRemove(requestParams.ProjectUri, out _), requestContext); + await RunWithErrorHandling(() => Projects.TryRemove(requestParams.ProjectUri!, out _), requestContext); } internal async Task HandleNewSqlProjectRequest(NewSqlProjectParams requestParams, RequestContext requestContext) { await RunWithErrorHandling(async () => { - await SqlProject.CreateProjectAsync(requestParams.ProjectUri, new CreateSqlProjectParams() { ProjectType = requestParams.SqlProjectType, DspVersion = requestParams.DatabaseSchemaProvider }); - GetProject(requestParams.ProjectUri); // load into the cache + await SqlProject.CreateProjectAsync(requestParams.ProjectUri!, new CreateSqlProjectParams() { ProjectType = requestParams.SqlProjectType, DspVersion = requestParams.DatabaseSchemaProvider }); + GetProject(requestParams.ProjectUri!); // load into the cache }, requestContext); } @@ -106,14 +106,14 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects { Success = true, ErrorMessage = null, - IsCrossPlatformCompatible = GetProject(requestParams.ProjectUri).CrossPlatformCompatible + IsCrossPlatformCompatible = GetProject(requestParams.ProjectUri!).CrossPlatformCompatible }; }, requestContext); } internal async Task HandleUpdateProjectForCrossPlatformRequest(SqlProjectParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).UpdateForCrossPlatform(), requestContext); + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!).UpdateForCrossPlatform(), requestContext); } #endregion @@ -124,17 +124,17 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects internal async Task HandleAddSqlObjectScriptRequest(SqlProjectScriptParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).SqlObjectScripts.Add(new SqlObjectScript(requestParams.Path)), requestContext); + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!).SqlObjectScripts.Add(new SqlObjectScript(requestParams.Path!)), requestContext); } internal async Task HandleDeleteSqlObjectScriptRequest(SqlProjectScriptParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).SqlObjectScripts.Delete(requestParams.Path), requestContext); + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!).SqlObjectScripts.Delete(requestParams.Path!), requestContext); } internal async Task HandleExcludeSqlObjectScriptRequest(SqlProjectScriptParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).SqlObjectScripts.Exclude(requestParams.Path), requestContext); + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!).SqlObjectScripts.Exclude(requestParams.Path!), requestContext); } #endregion @@ -193,11 +193,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects internal async Task HandleAddSystemDatabaseReferenceRequest(AddSystemDatabaseReferenceParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).DatabaseReferences.Add( + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!).DatabaseReferences.Add( new SystemDatabaseReference( requestParams.SystemDatabase, requestParams.SuppressMissingDependencies, - requestParams.DatabaseVariable)), + requestParams.DatabaseLiteral)), requestContext); } @@ -205,14 +205,32 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects { await RunWithErrorHandling(() => { - SqlProject project = GetProject(requestParams.ProjectUri); + requestParams.Validate(); - project.DatabaseReferences.Add( - new DacpacReference( + SqlProject project = GetProject(requestParams.ProjectUri!); + DacpacReference reference; + + if (requestParams.DatabaseLiteral != null) // same server, different database via database name literal + { + reference = new DacpacReference( requestParams.DacpacPath, requestParams.SuppressMissingDependencies, - project.SqlCmdVariables.Get(requestParams.DatabaseVariable), - project.SqlCmdVariables.Get(requestParams.ServerVariable))); + requestParams.DatabaseLiteral); + } + else if (requestParams.DatabaseVariable != null) // different database, possibly different server via sqlcmdvar + { + reference = new DacpacReference( + requestParams.DacpacPath, + requestParams.SuppressMissingDependencies, + project.SqlCmdVariables.Get(requestParams.DatabaseVariable!), + requestParams.ServerVariable != null ? project.SqlCmdVariables.Get(requestParams.ServerVariable) : null); + } + else // same database + { + reference = new DacpacReference(requestParams.DacpacPath, requestParams.SuppressMissingDependencies); + } + + project.DatabaseReferences.Add(reference); }, requestContext); } @@ -220,42 +238,64 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects { await RunWithErrorHandling(() => { - SqlProject project = GetProject(requestParams.ProjectUri); + requestParams.Validate(); - project.DatabaseReferences.Add( - new SqlProjectReference( + SqlProject project = GetProject(requestParams.ProjectUri!); + SqlProjectReference reference; + + if (requestParams.DatabaseLiteral != null) // same server, different database via database name literal + { + reference = new SqlProjectReference( requestParams.ProjectPath, requestParams.ProjectGuid, requestParams.SuppressMissingDependencies, - project.SqlCmdVariables.Get(requestParams.DatabaseVariable), - project.SqlCmdVariables.Get(requestParams.ServerVariable))); + requestParams.DatabaseLiteral); + } + else if (requestParams.DatabaseVariable != null) // different database, possibly different server via sqlcmdvar + { + reference = new SqlProjectReference( + requestParams.ProjectPath, + requestParams.ProjectGuid, requestParams.SuppressMissingDependencies, + project.SqlCmdVariables.Get(requestParams.DatabaseVariable!), + requestParams.ServerVariable != null ? project.SqlCmdVariables.Get(requestParams.ServerVariable) : null); + } + else // same database + { + reference = new SqlProjectReference( + requestParams.ProjectPath, + requestParams.ProjectGuid, + requestParams.SuppressMissingDependencies); + } + + project.DatabaseReferences.Add(reference); }, requestContext); } internal async Task HandleDeleteDatabaseReferenceRequest(DeleteDatabaseReferenceParams requestParams, RequestContext requestContext) { - await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).DatabaseReferences.Delete(requestParams.Name), requestContext); + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri!).DatabaseReferences.Delete(requestParams.Name!), requestContext); } #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); + 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); + 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); + 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); diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs index baf32e57..eb5d037d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs @@ -283,40 +283,77 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects Assert.IsFalse(File.Exists(scriptFullPath), $"{scriptFullPath} expected to have been deleted from disk"); } + #region Database reference tests + [Test] - public async Task TestDatabaseReferenceAddDelete() + public async Task TestDatabaseReferenceDelete() { - // Setup - SqlProjectsService service = new(); - string projectUri = await service.CreateSqlProject(); + var (service, projectUri, _, _) = await SetUpDatabaseReferenceTest(); - SqlCmdVariable databaseVar = new SqlCmdVariable("$(OtherDb)", "OtherDbDefaultValue", "OtherDbValue"); - SqlCmdVariable serverVar = new SqlCmdVariable("$(OtherServer)", "OtherServerDefaultValue", "OtherServerValue"); + // directly add a reference so there's something to delete + service.Projects[projectUri].DatabaseReferences.Add(new SystemDatabaseReference(SystemDatabase.Master, suppressMissingDependencies: true)); - service.Projects[projectUri].SqlCmdVariables.Add(databaseVar); - service.Projects[projectUri].SqlCmdVariables.Add(serverVar); + Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding reference"); - Assert.AreEqual(0, service.Projects[projectUri].DatabaseReferences.Count, "Baseline number of database references"); + MockRequest requestMock = new(); + await service.HandleDeleteDatabaseReferenceRequest(new DeleteDatabaseReferenceParams() + { + ProjectUri = projectUri, + Name = SystemDatabase.Master.ToString() + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleDeleteDatabaseReferenceRequest)); + Assert.AreEqual(0, service.Projects[projectUri].DatabaseReferences.Count, "Database references after deleting reference"); + } + + [Test] + public async Task TestSystemDatabaseReferenceAdd() + { + var (service, projectUri, _, _) = await SetUpDatabaseReferenceTest(); - // Validate adding a system database reference MockRequest requestMock = new(); await service.HandleAddSystemDatabaseReferenceRequest(new AddSystemDatabaseReferenceParams() { ProjectUri = projectUri, SystemDatabase = SystemDatabase.MSDB, - DatabaseVariable = "$(EmEssDeeBee)", + DatabaseLiteral = "EmEssDeeBee", SuppressMissingDependencies = false }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSystemDatabaseReferenceRequest)); 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); + SystemDatabaseReference systemDbRef = (SystemDatabaseReference)service.Projects[projectUri].DatabaseReferences.Get(SystemDatabase.MSDB.ToString()); Assert.AreEqual(SystemDatabase.MSDB, systemDbRef.SystemDb, "Referenced system DB"); - Assert.AreEqual("$(EmEssDeeBee)", systemDbRef.DatabaseVariableLiteralName); + Assert.AreEqual("EmEssDeeBee", systemDbRef.DatabaseVariableLiteralName); Assert.IsFalse(systemDbRef.SuppressMissingDependencies, nameof(systemDbRef.SuppressMissingDependencies)); + } - // Validate adding a dacpac reference - string mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabase.dacpac"); + [Test] + public async Task TestDacpacReferenceAdd() + { + var (service, projectUri, databaseVar, serverVar) = await SetUpDatabaseReferenceTest(); + + // Validate adding a dacpac reference on the same server + string mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabaseSameServer.dacpac"); + + MockRequest requestMock = new(); + await service.HandleAddDacpacReferenceRequest(new AddDacpacReferenceParams() + { + ProjectUri = projectUri, + DacpacPath = mockReferencePath, + SuppressMissingDependencies = false + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest), "same server"); + Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference (same server)"); + DacpacReference dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); + Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); + Assert.IsFalse(dacpacRef.SuppressMissingDependencies, nameof(dacpacRef.SuppressMissingDependencies)); + Assert.IsNull(dacpacRef.DatabaseVariableLiteralName, nameof(dacpacRef.DatabaseVariableLiteralName)); + Assert.IsNull(dacpacRef.DatabaseVariable, nameof(dacpacRef.DatabaseVariable)); + + // Validate adding a dacpac reference via SQLCMD variable + mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabaseSqlCmd.dacpac"); requestMock = new(); await service.HandleAddDacpacReferenceRequest(new AddDacpacReferenceParams() @@ -328,16 +365,60 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects ServerVariable = serverVar.Name }, requestMock.Object); - requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest)); - 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); + requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest), "sqlcmdvar"); + Assert.AreEqual(2, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference (sqlcmdvar)"); + dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); - Assert.AreEqual(databaseVar.Name, dacpacRef.DatabaseVariable.VarName); - Assert.AreEqual(serverVar.Name, dacpacRef.ServerVariable.VarName); + 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 - mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "OtherDatabase", "OtherDatabase.sqlproj"); + // Validate adding a dacpac reference via database literal + mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabaseLiteral.dacpac"); + + requestMock = new(); + await service.HandleAddDacpacReferenceRequest(new AddDacpacReferenceParams() + { + ProjectUri = projectUri, + DacpacPath = mockReferencePath, + SuppressMissingDependencies = false, + DatabaseLiteral = "DacpacLiteral" + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest), "db literal"); + Assert.AreEqual(3, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference (db literal)"); + dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); + Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); + Assert.AreEqual("DacpacLiteral", dacpacRef.DatabaseVariableLiteralName, nameof(dacpacRef.DatabaseVariableLiteralName)); + Assert.IsFalse(dacpacRef.SuppressMissingDependencies, nameof(dacpacRef.SuppressMissingDependencies)); + } + + [Test] + public async Task TestSqlProjectReferenceAdd() + { + var (service, projectUri, databaseVar, serverVar) = await SetUpDatabaseReferenceTest(); + + // Validate adding a project reference on the same server + string mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "SameDatabase", "SameDatabaseSqlCmd.sqlproj"); + + MockRequest requestMock = new(); + await service.HandleAddSqlProjectReferenceRequest(new AddSqlProjectReferenceParams() + { + ProjectUri = projectUri, + ProjectPath = mockReferencePath, + SuppressMissingDependencies = false + }, requestMock.Object); + + requestMock.AssertSuccess(nameof(service.HandleAddSqlProjectReferenceRequest), "same server"); + Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference (same server)"); + SqlProjectReference projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); + Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), projectRef.ProjectPath, "Referenced project"); + Assert.IsFalse(projectRef.SuppressMissingDependencies, nameof(projectRef.SuppressMissingDependencies)); + Assert.IsNull(projectRef.DatabaseVariableLiteralName, nameof(projectRef.DatabaseVariableLiteralName)); + Assert.IsNull(projectRef.DatabaseVariable, nameof(projectRef.DatabaseVariable)); + + // Validate adding a project reference via SQLCMD variable + mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "OtherDatabase", "OtherDatabaseSqlCmd.sqlproj"); requestMock = new(); await service.HandleAddSqlProjectReferenceRequest(new AddSqlProjectReferenceParams() @@ -351,27 +432,38 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlProjectReferenceRequest)); - Assert.AreEqual(3, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference"); - SqlProjectReference projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.First(x => x is SqlProjectReference); - Assert.AreEqual(mockReferencePath, projectRef.ProjectPath, "Referenced project"); + Assert.AreEqual(2, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference (sqlcmdvar)"); + projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); + Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), projectRef.ProjectPath, "Referenced project"); Assert.AreEqual(TEST_GUID, projectRef.ProjectGuid, "Referenced project GUID"); - Assert.AreEqual(databaseVar.Name, projectRef.DatabaseVariable.VarName); - Assert.AreEqual(serverVar.Name, projectRef.ServerVariable.VarName); + 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 + // Validate adding a project reference via database literal + mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "OtherDatabase", "OtherDatabaseLiteral.sqlproj"); + requestMock = new(); - await service.HandleDeleteDatabaseReferenceRequest(new DeleteDatabaseReferenceParams() + await service.HandleAddSqlProjectReferenceRequest(new AddSqlProjectReferenceParams() { ProjectUri = projectUri, - Name = mockReferencePath + ProjectPath = mockReferencePath, + ProjectGuid = TEST_GUID, + SuppressMissingDependencies = false, + DatabaseLiteral = "ProjectLiteral" }, requestMock.Object); - requestMock.AssertSuccess(nameof(service.HandleDeleteDatabaseReferenceRequest)); - Assert.AreEqual(2, service.Projects[projectUri].DatabaseReferences.Count, "Database references after deleting SQL project reference"); - Assert.IsFalse(service.Projects[projectUri].DatabaseReferences.Any(x => x is SqlProjectReference), "Database references list expected to not contain the SQL Project reference"); + requestMock.AssertSuccess(nameof(service.HandleAddSqlProjectReferenceRequest)); + Assert.AreEqual(3, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference (db literal)"); + projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); + Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), projectRef.ProjectPath, "Referenced project"); + Assert.AreEqual(TEST_GUID, projectRef.ProjectGuid, "Referenced project GUID"); + Assert.AreEqual("ProjectLiteral", projectRef.DatabaseVariableLiteralName); + Assert.IsFalse(projectRef.SuppressMissingDependencies, nameof(projectRef.SuppressMissingDependencies)); } + #endregion + [Test] public async Task TestFolderAddDelete() { @@ -497,6 +589,27 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects getRequestMock.AssertSuccess(nameof(service.HandleGetCrossPlatformCompatibilityRequest)); Assert.IsTrue(((GetCrossPlatformCompatiblityResult)getRequestMock.Result).IsCrossPlatformCompatible, "Input file should be cross-platform compatible after conversion"); } + + #region Helpers + + private async Task<(SqlProjectsService Service, string ProjectUri, SqlCmdVariable DatabaseVar, SqlCmdVariable ServerVar)> SetUpDatabaseReferenceTest() + { + // Setup + SqlProjectsService service = new(); + string projectUri = await service.CreateSqlProject(); + + SqlCmdVariable databaseVar = new SqlCmdVariable("$(OtherDb)", "OtherDbDefaultValue", "OtherDbValue"); + SqlCmdVariable serverVar = new SqlCmdVariable("$(OtherServer)", "OtherServerDefaultValue", "OtherServerValue"); + + service.Projects[projectUri].SqlCmdVariables.Add(databaseVar); + service.Projects[projectUri].SqlCmdVariables.Add(serverVar); + + Assert.AreEqual(0, service.Projects[projectUri].DatabaseReferences.Count, "Baseline number of database references"); + + return (service, projectUri, databaseVar, serverVar); + } + + #endregion } internal static class SqlProjectsExtensions diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlProjects/SqlProjectTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlProjects/SqlProjectTests.cs new file mode 100644 index 00000000..6bd26308 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlProjects/SqlProjectTests.cs @@ -0,0 +1,37 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts; +using NUnit.Framework; + +namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlProjects +{ + public class SqlProjectTests + { + [Test] + public void DatabaseReferenceValidationTest() + { + // Verify that Validate() throws when both DatabaseLiteral and DatabaseVariable are set + AddUserDatabaseReferenceParams reference = new AddDacpacReferenceParams() // any concrete class will do + { + DatabaseLiteral = "DatabaseName", + DatabaseVariable = "$(DatabaseVariable)" + }; + + Assert.Throws(() => reference.Validate(), $"Validate() for a reference with both {nameof(reference.DatabaseLiteral)} and {nameof(reference.DatabaseVariable)} should have failed"); + + // Verify that Validate() passes any other time + reference = new AddDacpacReferenceParams() { DatabaseLiteral = "DatabaseName" }; + reference.Validate(); + + reference = new AddDacpacReferenceParams() { DatabaseVariable = "$(DatabaseVariable)" }; + reference.Validate(); + + reference = new AddDacpacReferenceParams(); + reference.Validate(); + } + } +}