diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GetObjectsFromTSqlModelRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GetObjectsFromTSqlModelRequest.cs new file mode 100644 index 00000000..86cf8dc0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GetObjectsFromTSqlModelRequest.cs @@ -0,0 +1,33 @@ +// +// 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; + +namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts +{ + /// + /// Parameters to get objects from SQL model + /// + public class GetObjectsFromTSqlModelParams + { + /// + /// URI of the project file this model is for + /// + public string ProjectUri { get; set; } + + /// + /// Object types to query + /// + public string[] ObjectTypes { get; set; } + } + + /// + /// Defines the get objects sql model request + /// + class GetObjectsFromTSqlModelRequest + { + public static readonly RequestType Type = + RequestType.Create("dacFx/getObjectsFromTSqlModel"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/TSqlObjectInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/TSqlObjectInfo.cs new file mode 100644 index 00000000..cf93482c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/TSqlObjectInfo.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. +// + +namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts +{ + /// + /// Describes TSqlObject information + /// + public class TSqlObjectInfo + { + public string Name { get; set; } + + public string ObjectType { get; set; } + + public TSqlObjectRelationship[] ReferencedObjects { get; set; } + } + + /// + /// Describes objects referenced and their relationship type + /// + public class TSqlObjectRelationship + { + public string Name { get; set; } + + public string ObjectType { get; set; } + + public string RelationshipType { get; set; } + + public string FromObjectName { get; set; } + + public string FromObjectType { get; set; } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs index af5c4cd0..583767c6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs @@ -57,6 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx serviceHost.SetRequestHandler(GetDefaultPublishOptionsRequest.Type, this.HandleGetDefaultPublishOptionsRequest); serviceHost.SetRequestHandler(ParseTSqlScriptRequest.Type, this.HandleParseTSqlScriptRequest); serviceHost.SetRequestHandler(GenerateTSqlModelRequest.Type, this.HandleGenerateTSqlModelRequest); + serviceHost.SetRequestHandler(GetObjectsFromTSqlModelRequest.Type, this.HandleGetObjectsFromTSqlModelRequest); } /// @@ -286,6 +287,28 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx } } + /// + /// Handles request to get objects from sql model + /// + /// + public async Task HandleGetObjectsFromTSqlModelRequest(GetObjectsFromTSqlModelParams requestParams, RequestContext requestContext) + { + TSqlObjectInfo[] objectInfos = { }; + var model = projectModels.Value[requestParams.ProjectUri]; + + if (model == null) + { + await requestContext.SendError(new Exception(SR.SqlProjectModelNotFound(requestParams.ProjectUri))); + } + else + { + GetObjectsFromTSqlModelOperation operation = new GetObjectsFromTSqlModelOperation(requestParams, model); + objectInfos = operation.GetObjectsFromTSqlModel(); + await requestContext.SendResult(objectInfos); + } + return; + } + private void ExecuteOperation(DacFxOperation operation, DacFxParams parameters, string taskName, RequestContext requestContext) { Task.Run(async () => diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/GetObjectsFromTSqlModelOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GetObjectsFromTSqlModelOperation.cs new file mode 100644 index 00000000..d1d1697c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GetObjectsFromTSqlModelOperation.cs @@ -0,0 +1,77 @@ +// +// 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 System.Linq; +using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; +using Microsoft.SqlTools.Utility; +using Microsoft.SqlServer.Dac.Model; + +namespace Microsoft.SqlTools.ServiceLayer.DacFx +{ + /// + /// Class to represent request to get objects from model + /// + public class GetObjectsFromTSqlModelOperation + { + private TSqlModel Model; + public GetObjectsFromTSqlModelParams Parameters { get; } + + public GetObjectsFromTSqlModelOperation(GetObjectsFromTSqlModelParams parameters, TSqlModel model) + { + Validate.IsNotNull("parameters", parameters); + Validate.IsNotNull("model", model); + this.Parameters = parameters; + this.Model = model; + } + + /// + /// Get user defined top level type objects from model + /// + public TSqlObjectInfo[] GetObjectsFromTSqlModel() + { + try + { + var filters = Parameters.ObjectTypes.Select(t => MapType(t)).ToArray(); + var objects = Model.GetObjects(DacQueryScopes.UserDefined, filters).ToList(); + + return objects.Select(o => new TSqlObjectInfo + { + Name = o.Name.ToString(), + ObjectType = o.ObjectType.Name, + ReferencedObjects = o.GetReferencedRelationshipInstances().ToList().Select(r => new TSqlObjectRelationship + { + Name = r.ObjectName.ToString(), + ObjectType = r.Object.ObjectType.Name, + RelationshipType = r.Relationship.Type.ToString(), + FromObjectName = r.FromObject.Name.ToString(), + FromObjectType = r.FromObject.ObjectType.Name + }).ToArray() + }).ToArray(); + } + catch (Exception ex) + { + Logger.Error(new Exception(SR.GetUserDefinedObjectsFromModelFailed, ex)); + throw; + } + } + + /// + /// Class to represent the type of objects to query within the sql model + /// + public static ModelTypeClass MapType(string type) + { + switch (type.ToLower()) + { + case "table": + return ModelSchema.Table; + case "view": + return ModelSchema.View; + default: + throw new ArgumentException(SR.UnsupportedModelType(type)); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index ce8e7b81..3d79884b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -9429,6 +9429,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string GetUserDefinedObjectsFromModelFailed + { + get + { + return Keys.GetString(Keys.GetUserDefinedObjectsFromModelFailed); + } + } + public static string ConnectionServiceListDbErrorNotConnected(string uri) { return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); @@ -9814,6 +9822,16 @@ namespace Microsoft.SqlTools.ServiceLayer return Keys.GetString(Keys.ComputedColumnNeedToBePersistedInForeignKeyRuleDescription, columnName, foreignKeyName); } + public static string SqlProjectModelNotFound(string projectUri) + { + return Keys.GetString(Keys.SqlProjectModelNotFound, projectUri); + } + + public static string UnsupportedModelType(string type) + { + return Keys.GetString(Keys.UnsupportedModelType, type); + } + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Keys { @@ -13578,6 +13596,15 @@ namespace Microsoft.SqlTools.ServiceLayer public const string ComputedColumnNeedToBePersistedInForeignKeyRuleDescription = "ComputedColumnNeedToBePersistedInForeignKeyRuleDescription"; + public const string SqlProjectModelNotFound = "SqlProjectModelNotFound"; + + + public const string UnsupportedModelType = "UnsupportedModelType"; + + + public const string GetUserDefinedObjectsFromModelFailed = "GetUserDefinedObjectsFromModelFailed"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 21d4d2b4..ff660017 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -5206,4 +5206,18 @@ The Query Processor estimates that implementing the following index could improv . Parameters: 0 - columnName (string), 1 - foreignKeyName (string) + + Could not find SQL model from project: {0}. + . + Parameters: 0 - projectUri (string) + + + Unsupported model type: {0}. + . + Parameters: 0 - type (string) + + + Failed to get user defined objects from model. + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 466e7ad8..e179664b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2381,4 +2381,11 @@ ClusteredIndexCannotHaveFilterPredicateRuleDescription = Filter predicate is not ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription(string columnName, string indexName, int rowNumber) = Column with name '{0}' has already been included to the index '{1}'. Row number: {2}. ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription(string columnName, string indexName, int rowNumber) = Included column with name '{0}' has already been part of the index '{1}' and it cannot be included. Row number: {2}. ComputedColumnNeedToBePersistedAndNotNullInPrimaryKeyRuleDescription(string columnName) = The computed column with name '{0}' has to be persisted and not nullable to be part of a primary key. -ComputedColumnNeedToBePersistedInForeignKeyRuleDescription(string columnName, string foreignKeyName) = The computed column with name '{0}' has to be persisted to be part of the foreign key '{1}'. \ No newline at end of file +ComputedColumnNeedToBePersistedInForeignKeyRuleDescription(string columnName, string foreignKeyName) = The computed column with name '{0}' has to be persisted to be part of the foreign key '{1}'. + +############################################################################ +# TSql Model + +SqlProjectModelNotFound(string projectUri) = Could not find SQL model from project: {0}. +UnsupportedModelType(string type) = Unsupported model type: {0}. +GetUserDefinedObjectsFromModelFailed = Failed to get user defined objects from model. \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 2611c891..f3235e5a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -6331,6 +6331,23 @@ The Query Processor estimates that implementing the following index could improv Dropped Ledger Views + + Could not find SQL model from project: {0}. + Could not find SQL model from project: {0}. + . + Parameters: 0 - projectUri (string) + + + Unsupported model type: {0}. + Unsupported model type: {0}. + . + Parameters: 0 - type (string) + + + Failed to get user defined objects from model. + Failed to get user defined objects from model. + + The computed column with name '{0}' has to be persisted and not nullable to be part of a primary key. The computed column with name '{0}' has to be persisted and not nullable to be part of a primary key. diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs index c460e088..d3791d02 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs @@ -1057,4 +1057,57 @@ public class TSqlModelRequestTests await service.HandleGenerateTSqlModelRequest(generateTSqlScriptParams, requestContext.Object); Assert.That(service.projectModels.Value, Contains.Key(generateTSqlScriptParams.ProjectUri), "Model was not stored under project uri"); } + + /// + /// Verify the get objects TSql Model handle + /// + [Test] + public async Task VerifyGetObjectsFromTSqlModelHandle() + { + string sqlTable1DefinitionPath = Path.Join(TSqlModelTestFolder, "table1.sql"); + string sqlTable2DefinitionPath = Path.Join(TSqlModelTestFolder, "table2.sql"); + string view1DefinitionPath = Path.Join(TSqlModelTestFolder, "view1.sql"); + const string table1 = @"CREATE TABLE [dbo].[table1] + ( + [ID] INT NOT NULL PRIMARY KEY, + )"; + const string table2 = @"CREATE TABLE [dbo].[table2] + ( + [ID] INT NOT NULL PRIMARY KEY, + )"; + const string view1 = "CREATE VIEW [dbo].[view1] AS SELECT dbo.table1.* FROM dbo.table1"; + // create sql file + File.WriteAllText(sqlTable1DefinitionPath, table1); + File.WriteAllText(sqlTable2DefinitionPath, table2); + File.WriteAllText(view1DefinitionPath, view1); + + var generateTSqlScriptParams = new GenerateTSqlModelParams + { + ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), + ModelTargetVersion = "Sql160", + FilePaths = new[] { sqlTable1DefinitionPath, sqlTable2DefinitionPath } + }; + + GenerateTSqlModelOperation op = new GenerateTSqlModelOperation(generateTSqlScriptParams); + var model = op.GenerateTSqlModel(); + + service.projectModels.Value.TryAdd(generateTSqlScriptParams.ProjectUri, model); + + var getObjectsParams = new GetObjectsFromTSqlModelParams + { + ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), + ObjectTypes = new[] { "Table" } + }; + + var requestContext = new Mock>(); + var actualResponse = new List(); + requestContext.Setup(x => x.SendResult(It.IsAny())) + .Callback(actual => actualResponse = actual.ToList()) + .Returns(Task.CompletedTask); + await service.HandleGetObjectsFromTSqlModelRequest(getObjectsParams, requestContext.Object); + + Assert.IsNotNull(actualResponse); + Assert.AreEqual(actualResponse.Count, 2); + CollectionAssert.AreEquivalent(actualResponse.Select(o => o.Name), new[] { "[dbo].[table1]", "[dbo].[table2]" }, "Table names do not match"); + } }