From 1342a8a085c0e40b59f4fd04cd8210c6d9a85101 Mon Sep 17 00:00:00 2001 From: Sai Avishkar Sreerama <74571829+ssreerama@users.noreply.github.com> Date: Fri, 5 Aug 2022 15:28:39 -0500 Subject: [PATCH] Object Types are getting from DacFx ObjectTypes (#1574) * Include Object Types are getting from DacFx * All tests are passing now with excludObjectType casting * Test fix with new ExccvludeObjectType change * Added test case for the include objects types * Updated name to objectTypesDictionary and objectType getName from Display attribute * code Review updates * Removing Exclude word logic here as the options were updated on DacFx * updating the null check * DacFx vBump * code updates according to review comments --- Packages.props | 2 +- .../DacFx/Contracts/DeploymentOptions.cs | 121 +++++++++++++----- .../SchemaCompare/SchemaCompareUtils.cs | 25 +++- .../DacFx/DacFxServiceTests.cs | 35 ++++- .../SchemaCompareServiceOptionsTests.cs | 52 ++++---- .../SchemaCompareServiceTests.cs | 4 +- .../SchemaCompare/SchemaCompareTestUtils.cs | 2 +- 7 files changed, 170 insertions(+), 71 deletions(-) diff --git a/Packages.props b/Packages.props index 35f9defe..2f4c447b 100644 --- a/Packages.props +++ b/Packages.props @@ -22,7 +22,7 @@ - + diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeploymentOptions.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeploymentOptions.cs index f7d87ff2..1fe08c8d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeploymentOptions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeploymentOptions.cs @@ -2,14 +2,17 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.Utility; using System; +using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading.Tasks; -using Microsoft.SqlServer.Dac; using System.Reflection; -using System.Collections.Generic; +using System.Threading.Tasks; namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts { @@ -50,31 +53,36 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts public class DeploymentOptions { #region Properties - public DeploymentOptionProperty ExcludeObjectTypes { get; set; } = new DeploymentOptionProperty + + /// + /// These default exclude options are for schema compare extension, It require some default options to be excluded for SC operations + /// Where as the publish operation does not require any defaults, removing all default options for publish is handled in \extensions\sql-database-projects\src\dialogs\publishDatabaseDialog.ts + /// + public DeploymentOptionProperty ExcludeObjectTypes { get; set; } = new DeploymentOptionProperty ( - new ObjectType[] { - ObjectType.ServerTriggers, - ObjectType.Routes, - ObjectType.LinkedServerLogins, - ObjectType.Endpoints, - ObjectType.ErrorMessages, - ObjectType.Files, - ObjectType.Logins, - ObjectType.LinkedServers, - ObjectType.Credentials, - ObjectType.DatabaseScopedCredentials, - ObjectType.DatabaseEncryptionKeys, - ObjectType.MasterKeys, - ObjectType.DatabaseAuditSpecifications, - ObjectType.Audits, - ObjectType.ServerAuditSpecifications, - ObjectType.CryptographicProviders, - ObjectType.ServerRoles, - ObjectType.EventSessions, - ObjectType.DatabaseOptions, - ObjectType.EventNotifications, - ObjectType.ServerRoleMembership, - ObjectType.AssemblyFiles + new string[] { + Enum.GetName(ObjectType.ServerTriggers), + Enum.GetName(ObjectType.Routes), + Enum.GetName(ObjectType.LinkedServerLogins), + Enum.GetName(ObjectType.Endpoints), + Enum.GetName(ObjectType.ErrorMessages), + Enum.GetName(ObjectType.Files), + Enum.GetName(ObjectType.Logins), + Enum.GetName(ObjectType.LinkedServers), + Enum.GetName(ObjectType.Credentials), + Enum.GetName(ObjectType.DatabaseScopedCredentials), + Enum.GetName(ObjectType.DatabaseEncryptionKeys), + Enum.GetName(ObjectType.MasterKeys), + Enum.GetName(ObjectType.DatabaseAuditSpecifications), + Enum.GetName(ObjectType.Audits), + Enum.GetName(ObjectType.ServerAuditSpecifications), + Enum.GetName(ObjectType.CryptographicProviders), + Enum.GetName(ObjectType.ServerRoles), + Enum.GetName(ObjectType.EventSessions), + Enum.GetName(ObjectType.DatabaseOptions), + Enum.GetName(ObjectType.EventNotifications), + Enum.GetName(ObjectType.ServerRoleMembership), + Enum.GetName(ObjectType.AssemblyFiles) } ); @@ -83,6 +91,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts /// public Dictionary> BooleanOptionsDictionary { get; set; } = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); + /// + /// Contains object types enum name and its display name from \Product\Source\DeploymentApi\ObjectTypes.cs Enum + /// key: optionName, value:DisplayName + /// + public Dictionary ObjectTypesDictionary = new Dictionary(StringComparer.InvariantCultureIgnoreCase); #endregion public DeploymentOptions() @@ -165,6 +178,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts this.BooleanOptionsDictionary[prop.Name] = (DeploymentOptionProperty)setProp; } } + + // Preparing object types dictionary + InitializeObjectTypesDictionary(); } public void SetOptions(DacDeployOptions options) @@ -193,6 +209,30 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts } } + /// + /// Preparing all object types which are considered as boolean options + /// + public void InitializeObjectTypesDictionary() + { + Type objectTypeEnum = typeof(ObjectType); + foreach (string name in Enum.GetNames(objectTypeEnum)) + { + MemberInfo[] member = objectTypeEnum.GetMember(name); + MemberInfo info = member?.FirstOrDefault(); + string displayName = info?.GetCustomAttribute().GetName(); + if (string.IsNullOrEmpty(displayName)) + { + // not expecting display name for any options as empty string + Logger.Write(TraceEventType.Error, string.Format($"Display name is empty for the Object type enum {0}", name)); + } + else + { + // Add the property to the Dictionary + ObjectTypesDictionary[name] = displayName; + } + } + } + /// /// Prepares and returns the value and description of a property /// @@ -206,10 +246,27 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts Type type = val != null ? typeof(DeploymentOptionProperty<>).MakeGenericType(val.GetType()) : typeof(DeploymentOptionProperty<>).MakeGenericType(prop.PropertyType); - object setProp = Activator.CreateInstance(type, val, - (descriptionAttribute != null ? descriptionAttribute.Description : ""), - (displayNameAttribute != null ? displayNameAttribute.DisplayName : "")); - return setProp; + // DeploymentOptions ExcludeObjectTypes are String[] type and need special casting here + if (prop.Name == nameof(this.ExcludeObjectTypes)) + { + type = typeof(DeploymentOptionProperty); + val = val != null ? ConvertObjectTypeToStringArray((ObjectType[])val): new string[] { }; + } + + return Activator.CreateInstance(type, val, + (descriptionAttribute != null ? descriptionAttribute.Description : string.Empty), + (displayNameAttribute != null ? displayNameAttribute.DisplayName : string.Empty)); + } + + /// + /// Converting ObjectType to String[] as the deployemnt options excludeObjectTypes is string[] but the DacFx DacDeployOptions excludeObjectTypes is of ObjectType[] + /// Loading options from profile and schema compare .scmp file should need this conversion + /// + /// + /// string[] + public string[] ConvertObjectTypeToStringArray(ObjectType[] excludeObjectTypes) + { + return excludeObjectTypes.Select(t => t.ToString()).ToArray(); } public static DeploymentOptions GetDefaultSchemaCompareOptions() @@ -221,7 +278,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts { DeploymentOptions result = new DeploymentOptions(); - result.ExcludeObjectTypes.Value = result.ExcludeObjectTypes.Value.Where(x => x != ObjectType.DatabaseScopedCredentials).ToArray(); // re-include database-scoped credentials + result.ExcludeObjectTypes.Value = result.ExcludeObjectTypes.Value.Where(x => x != Enum.GetName(ObjectType.DatabaseScopedCredentials)).ToArray(); // re-include database-scoped credentials return result; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs index 5e4d610a..ad3ef6d1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareUtils.cs @@ -43,15 +43,30 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare foreach (PropertyInfo deployOptionsProp in deploymentOptionsProperties) { var prop = propType.GetProperty(deployOptionsProp.Name); - if (prop != null) + // Set the excludeObjectTypes values to the DacDeployOptions + if (prop != null && deployOptionsProp.Name == nameof(deploymentOptions.ExcludeObjectTypes)) { + List finalExcludeObjects = new List { }; var val = deployOptionsProp.GetValue(deploymentOptions); - var selectedVal = val.GetType().GetProperty("Value").GetValue(val); + string[] excludeObjectTypeOptionsArray = (string[])val.GetType().GetProperty("Value").GetValue(val); - // Set the excludeObjectTypes values to the DacDeployOptions - if (selectedVal != null && deployOptionsProp.Name == nameof(deploymentOptions.ExcludeObjectTypes)) + if (excludeObjectTypeOptionsArray != null) { - prop.SetValue(dacOptions, selectedVal); + foreach(string objectTypeValue in excludeObjectTypeOptionsArray) + { + ObjectType objectTypeName = new ObjectType(); + + if (objectTypeValue != null && Enum.TryParse(objectTypeValue, ignoreCase: true, out objectTypeName)) + { + finalExcludeObjects.Add(objectTypeName); + } + else + { + Logger.Write(TraceEventType.Error, string.Format($"{objectTypeValue} is not part of ObjectTypes enum")); + } + } + // set final values to excludeObjectType property + prop.SetValue(dacOptions, finalExcludeObjects.ToArray()); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs index 70addd64..c460e088 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs @@ -589,7 +589,7 @@ FROM MissingEdgeHubInputStream'"; UpgradeExisting = true, DeploymentOptions = new DeploymentOptions() { - ExcludeObjectTypes = new DeploymentOptionProperty(new[] { ObjectType.Views }) + ExcludeObjectTypes = new DeploymentOptionProperty(new[] { Enum.GetName(ObjectType.Views) }) } }; @@ -669,7 +669,7 @@ FROM MissingEdgeHubInputStream'"; DatabaseName = targetDb.DatabaseName, DeploymentOptions = new DeploymentOptions() { - ExcludeObjectTypes = new DeploymentOptionProperty(new[] { ObjectType.Views }) + ExcludeObjectTypes = new DeploymentOptionProperty(new[] { Enum.GetName(ObjectType.Views) }) } }; @@ -689,7 +689,7 @@ FROM MissingEdgeHubInputStream'"; DatabaseName = targetDb.DatabaseName, DeploymentOptions = new DeploymentOptions() { - ExcludeObjectTypes = new DeploymentOptionProperty(new[] { ObjectType.Views }) + ExcludeObjectTypes = new DeploymentOptionProperty(new[] { Enum.GetName(ObjectType.Views) }) } }; @@ -839,6 +839,33 @@ Streaming query statement contains a reference to missing output stream 'Missing dacfxRequestContext.VerifyAll(); } + /// + /// Verify Object Types Dictionary items with ObjectType Enum members + /// + /// + [Test] + public void ValidateObjectTypesOptionswithEnum() + { + DeploymentOptions options = new DeploymentOptions(); + + // Verify the object types dictionary should exists + Assert.That(options.ObjectTypesDictionary, Is.Not.Null, "Object types dictionary is empty"); + + // Verify that the objects dictionary has all the item from Enum + Assert.That(options.ObjectTypesDictionary.Count, Is.EqualTo(Enum.GetNames(typeof(ObjectType)).Length), @"ObjectTypesDictionary is missing these objectTypes: {0}", + string.Join(", ", Enum.GetNames(typeof(ObjectType)).Except(options.ObjectTypesDictionary.Keys))); + + // Verify the options in the objects dictionary exists in the ObjectType Enum + foreach (var objTypeRow in options.ObjectTypesDictionary) + { + // Verify the option exists in ObjectType Enum + Assert.That(Enum.IsDefined(typeof(ObjectType), objTypeRow.Key), Is.True, $"{objTypeRow.Key} is not an enum member"); + + // Verify the options display name exists + Assert.That(objTypeRow.Value, Is.Not.Empty, $"Display name for the option {objTypeRow.Key} is empty"); + } + } + private bool ValidateStreamingJobErrors(ValidateStreamingJobResult expected, ValidateStreamingJobResult actual) { return expected.Success == actual.Success @@ -860,7 +887,7 @@ Streaming query statement contains a reference to missing output stream 'Missing if (v.Name == nameof(DeploymentOptions.ExcludeObjectTypes)) { - Assert.True((defaultP as ObjectType[])?.Length == (actualP as ObjectType[])?.Length, "Number of excluded objects is different not equal"); + Assert.True((defaultP as string[])?.Length == (actualP as string[])?.Length, "Number of excluded objects is different not equal"); } else { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceOptionsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceOptionsTests.cs index c6d980bf..1ac95a93 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceOptionsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceOptionsTests.cs @@ -79,33 +79,33 @@ END private DeploymentOptions GetExcludeTableValuedFunctionOptions() { var options = new DeploymentOptions(); - options.ExcludeObjectTypes = new DeploymentOptionProperty + options.ExcludeObjectTypes = new DeploymentOptionProperty ( - new ObjectType[]{ - ObjectType.ServerTriggers, - ObjectType.Routes, - ObjectType.LinkedServerLogins, - ObjectType.Endpoints, - ObjectType.ErrorMessages, - ObjectType.Filegroups, - ObjectType.Files, - ObjectType.Logins, - ObjectType.LinkedServers, - ObjectType.Credentials, - ObjectType.DatabaseScopedCredentials, - ObjectType.DatabaseEncryptionKeys, - ObjectType.MasterKeys, - ObjectType.DatabaseAuditSpecifications, - ObjectType.Audits, - ObjectType.ServerAuditSpecifications, - ObjectType.CryptographicProviders, - ObjectType.ServerRoles, - ObjectType.EventSessions, - ObjectType.DatabaseOptions, - ObjectType.EventNotifications, - ObjectType.ServerRoleMembership, - ObjectType.AssemblyFiles, - ObjectType.TableValuedFunctions, //added Functions to excluded types + new string[]{ + Enum.GetName(ObjectType.ServerTriggers), + Enum.GetName(ObjectType.Routes), + Enum.GetName(ObjectType.LinkedServerLogins), + Enum.GetName(ObjectType.Endpoints), + Enum.GetName(ObjectType.ErrorMessages), + Enum.GetName(ObjectType.Filegroups), + Enum.GetName(ObjectType.Files), + Enum.GetName(ObjectType.Logins), + Enum.GetName(ObjectType.LinkedServers), + Enum.GetName(ObjectType.Credentials), + Enum.GetName(ObjectType.DatabaseScopedCredentials), + Enum.GetName(ObjectType.DatabaseEncryptionKeys), + Enum.GetName(ObjectType.MasterKeys), + Enum.GetName(ObjectType.DatabaseAuditSpecifications), + Enum.GetName(ObjectType.Audits), + Enum.GetName(ObjectType.ServerAuditSpecifications), + Enum.GetName(ObjectType.CryptographicProviders), + Enum.GetName(ObjectType.ServerRoles), + Enum.GetName(ObjectType.EventSessions), + Enum.GetName(ObjectType.DatabaseOptions), + Enum.GetName(ObjectType.EventNotifications), + Enum.GetName(ObjectType.ServerRoleMembership), + Enum.GetName(ObjectType.AssemblyFiles), + Enum.GetName(ObjectType.TableValuedFunctions), //added Functions to excluded types } ); return options; diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs index e28967e5..3b02f2df 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs @@ -1356,8 +1356,8 @@ WITH VALUES DeploymentOptions options = new DeploymentOptions(); // ensure that files are excluded seperate from filegroups - Assert.True(options.ExcludeObjectTypes.Value.Contains(SqlServer.Dac.ObjectType.Files)); - Assert.False(options.ExcludeObjectTypes.Value.Contains(SqlServer.Dac.ObjectType.Filegroups)); + Assert.True(options.ExcludeObjectTypes.Value.Contains(Enum.GetName(SqlServer.Dac.ObjectType.Files))); + Assert.False(options.ExcludeObjectTypes.Value.Contains(Enum.GetName(SqlServer.Dac.ObjectType.Filegroups))); var schemaCompareParams = new SchemaCompareParams { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareTestUtils.cs index 51f2a3a2..97b52935 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareTestUtils.cs @@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SchemaCompare if (v.Name == nameof(DeploymentOptions.ExcludeObjectTypes)) { - Assert.That((defaultPValue as ObjectType[]).Length, Is.EqualTo((actualPValue as ObjectType[]).Length), $"Number of excluded objects is different; expected: {(defaultPValue as ObjectType[]).Length} actual: {(actualPValue as ObjectType[]).Length}"); + Assert.That((defaultPValue as string[]).Length, Is.EqualTo((actualPValue as string[]).Length), $"Number of excluded objects is different."); } else {