diff --git a/Packages.props b/Packages.props
index cd4afd99..5b389bf4 100644
--- a/Packages.props
+++ b/Packages.props
@@ -21,7 +21,7 @@
-
+
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
deleted file mode 100644
index 9fef7734..00000000
Binary files a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8093.0-alpha.nupkg and /dev/null differ
diff --git a/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8416.0-alpha.nupkg b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8416.0-alpha.nupkg
new file mode 100644
index 00000000..f2d96221
Binary files /dev/null and b/bin/nuget/Microsoft.SqlServer.DacFx.Projects.161.8416.0-alpha.nupkg differ
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetProjectProperties.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetProjectProperties.cs
new file mode 100644
index 00000000..cb3033a8
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/GetProjectProperties.cs
@@ -0,0 +1,58 @@
+//
+// 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;
+
+#nullable disable
+
+namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts
+{
+ ///
+ /// Get the cross-platform compatibility status for a project
+ ///
+ public class GetProjectPropertiesRequest
+ {
+ public static readonly RequestType Type = RequestType.Create("sqlProjects/getProjectProperties");
+ }
+
+ ///
+ /// Result containing project properties contained in the .sqlproj XML
+ ///
+ public class GetProjectPropertiesResult : ResultStatus
+ {
+ ///
+ /// GUID for the SQL project
+ ///
+ public string ProjectGuid { get; set; }
+
+ ///
+ /// Build configuration, defaulted to Debug if not specified
+ ///
+ public string Configuration { get; set; }
+
+ ///
+ /// Build platform, defaulted to AnyCPU if not specified
+ ///
+ public string Platform { get; set; }
+
+ ///
+ /// Output path for build, defaulted to "bin/Debug" if not specified.
+ /// May be absolute or relative.
+ ///
+ public string OutputPath { get; set; }
+
+ ///
+ /// Default collation for the project, defaulted to SQL_Latin1_General_CP1_CI_AS if not specified
+ ///
+ public string DefaultCollation { get; set; }
+
+ ///
+ /// Source of the database schema, used in telemetry
+ ///
+ public string? DatabaseSource { get; set; }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/SetDatabaseSource.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/SetDatabaseSource.cs
new file mode 100644
index 00000000..b11b1910
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Projects/SetDatabaseSource.cs
@@ -0,0 +1,31 @@
+//
+// 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
+{
+ ///
+ /// Parameters for setting the DatabaseSource property of a .sqlproj file
+ ///
+ public class SetDatabaseSourceParams : SqlProjectParams
+ {
+ ///
+ /// Source of the database schema, used in telemetry
+ ///
+ public string DatabaseSource { get; set; }
+ }
+
+ ///
+ /// Set the DatabaseSource property of a .sqlproj file
+ ///
+ public class SetDatabaseSourceRequest
+ {
+ public static readonly RequestType Type = RequestType.Create("sqlProjects/setDatabaseSource");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs
index 66a4a1a4..253a9fa9 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs
@@ -46,6 +46,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects
serviceHost.SetRequestHandler(CreateSqlProjectRequest.Type, HandleCreateSqlProjectRequest, isParallelProcessingSupported: true);
serviceHost.SetRequestHandler(GetCrossPlatformCompatibilityRequest.Type, HandleGetCrossPlatformCompatibilityRequest, isParallelProcessingSupported: true);
serviceHost.SetRequestHandler(UpdateProjectForCrossPlatformRequest.Type, HandleUpdateProjectForCrossPlatformRequest, isParallelProcessingSupported: false);
+ serviceHost.SetRequestHandler(GetProjectPropertiesRequest.Type, HandleGetProjectPropertiesRequest, isParallelProcessingSupported: true);
+ serviceHost.SetRequestHandler(SetDatabaseSourceRequest.Type, HandleSetDatabaseSourceRequest, isParallelProcessingSupported: false);
// SQL object script functions
serviceHost.SetRequestHandler(GetSqlObjectScriptsRequest.Type, HandleGetSqlObjectScriptsRequest, isParallelProcessingSupported: true);
@@ -133,6 +135,31 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects
await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).UpdateForCrossPlatform(), requestContext);
}
+ internal async Task HandleGetProjectPropertiesRequest(SqlProjectParams requestParams, RequestContext requestContext)
+ {
+ await RunWithErrorHandling(() =>
+ {
+ SqlProjectProperties props = GetProject(requestParams.ProjectUri).Properties;
+
+ return new GetProjectPropertiesResult()
+ {
+ Success = true,
+ ErrorMessage = null,
+ ProjectGuid = props.ProjectGuid,
+ Configuration = props.Configuration,
+ Platform = props.Platform,
+ OutputPath = props.OutputPath,
+ DefaultCollation = props.DefaultCollation,
+ DatabaseSource = props.DatabaseSource,
+ };
+ }, requestContext);
+ }
+
+ internal async Task HandleSetDatabaseSourceRequest(SetDatabaseSourceParams requestParams, RequestContext requestContext)
+ {
+ await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).Properties.DatabaseSource = requestParams.DatabaseSource, requestContext);
+ }
+
#endregion
#region Script/folder functions
@@ -310,7 +337,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects
#endregion
- #region Database Reference functions
+ #region Database reference functions
internal async Task HandleGetDatabaseReferencesRequest(SqlProjectParams requestParams, RequestContext requestContext)
{
@@ -459,7 +486,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects
{
if (!Projects.ContainsKey(projectUri))
{
- Projects[projectUri] = new SqlProject(projectUri);
+ Projects[projectUri] = SqlProject.OpenProject(projectUri);
}
return Projects[projectUri];
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs
index 48b04201..b004e9d5 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs
@@ -161,7 +161,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects
// Validate moving a SQL object script
string movedScriptRelativePath = @"SubPath\MyRenamedTable.sql";
- string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), movedScriptRelativePath);
+ string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath));
Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!);
requestMock = new();
@@ -254,7 +254,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects
// Validate moving a None script
string movedScriptRelativePath = @"SubPath\RenamedNoneIncludeFile.json";
- string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), movedScriptRelativePath);
+ string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath));
Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!);
requestMock = new();
@@ -343,7 +343,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects
// Validate moving a pre-deployment object script
string movedScriptRelativePath = @"SubPath\RenamedPreDeploymentScript.sql";
- string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), movedScriptRelativePath);
+ string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath));
Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!);
requestMock = new();
@@ -432,7 +432,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects
// Validate moving a post-deployment object script
string movedScriptRelativePath = @"SubPath\RenamedPostDeploymentScript.sql";
- string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), movedScriptRelativePath);
+ string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath));
Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!);
requestMock = new();
@@ -823,6 +823,53 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects
Assert.IsTrue(((GetCrossPlatformCompatibilityResult)getRequestMock.Result).IsCrossPlatformCompatible, "Input file should be cross-platform compatible after conversion");
}
+ [Test]
+ public async Task TestProjectProperties()
+ {
+ SqlProjectsService service = new();
+ string projectUri = await service.CreateSqlProject();
+
+ MockRequest mock = new();
+
+ await service.HandleGetProjectPropertiesRequest(new SqlProjectParams()
+ {
+ ProjectUri = projectUri
+ }, mock.Object);
+
+ mock.AssertSuccess(nameof(service.HandleGetProjectPropertiesRequest));
+
+ Assert.IsTrue(Guid.TryParse(mock.Result.ProjectGuid, out _), $"{mock.Result.ProjectGuid} should be set");
+ Assert.AreEqual("AnyCPU", mock.Result.Platform);
+ Assert.AreEqual("Debug", mock.Result.Configuration);
+ Assert.AreEqual(@"bin\Debug\", mock.Result.OutputPath); // default value is normalized to Windows slashes
+ Assert.AreEqual("SQL_Latin1_General_CP1_CI_AS", mock.Result.DefaultCollation);
+ Assert.IsNull(mock.Result.DatabaseSource, nameof(mock.Result.DatabaseSource)); // validate DatabaseSource is null when the tag isn't present
+
+ // Validate that DatabaseSource can be set when the tag doesn't exist
+
+ MockRequest setSourceMock = new();
+ await service.HandleSetDatabaseSourceRequest(new SetDatabaseSourceParams()
+ {
+ ProjectUri = projectUri,
+ DatabaseSource = "TestSource"
+ }, setSourceMock.Object);
+
+ setSourceMock.AssertSuccess(nameof(service.HandleSetDatabaseSourceRequest));
+ Assert.AreEqual("TestSource", service.Projects[projectUri].Properties.DatabaseSource);
+
+ // Validate DatabaseSource is read when it has a value
+
+ mock = new();
+
+ await service.HandleGetProjectPropertiesRequest(new SqlProjectParams()
+ {
+ ProjectUri = projectUri
+ }, mock.Object);
+
+ mock.AssertSuccess(nameof(service.HandleGetProjectPropertiesRequest));
+ Assert.AreEqual("TestSource", mock.Result.DatabaseSource);
+ }
+
#region Helpers
private async Task<(SqlProjectsService Service, string ProjectUri, SqlCmdVariable DatabaseVar, SqlCmdVariable ServerVar)> SetUpDatabaseReferenceTest()
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/FileUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/FileUtils.cs
index 22b58097..c3f21dd9 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/FileUtils.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/FileUtils.cs
@@ -80,13 +80,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common
}
///
- /// Normalizes Windows, Unix, and mixed paths to the same slash direction, specified by
+ /// Normalizes Windows, Unix, and mixed paths to the same slash direction, specified by .
///
///
- /// Win32NT for \, Unix for /
+ /// Win32NT for \, Unix for /. If not set, path will be normalized to the current platform.
///
- public static string NormalizePath(string path, PlatformID separatorType)
+ public static string NormalizePath(string path, PlatformID? separatorType = null)
{
+ separatorType ??= Environment.OSVersion.Platform;
+
return separatorType switch
{
PlatformID.Win32NT => path.Contains('/')