diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs index 0a3b9cac..2ef3026f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs @@ -46,6 +46,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin public DateTime lastLogBackupDate; public DatabaseUserAccess restrictAccess; public DatabaseStatus databaseState; + public DatabaseScopedConfigurationCollection databaseScopedConfigurations; public DefaultCursor defaultCursor; public CompatibilityLevel databaseCompatibilityLevel; public ContainmentType databaseContainmentType; @@ -556,6 +557,11 @@ WHERE do.database_id = @DbID this.isLedger = db.IsLedger; } + if (db.IsSupportedObject()) + { + this.databaseScopedConfigurations = db.DatabaseScopedConfigurations; + } + //Only fill in the Azure properties when connected to an Azure server if (context.Server.ServerType == DatabaseEngineType.SqlAzureDatabase) { @@ -689,6 +695,7 @@ WHERE do.database_id = @DbID this.maxSize = other.maxSize == null ? null : new DbSize(other.maxSize); this.backupStorageRedundancy = other.backupStorageRedundancy; this.isLedger = other.isLedger; + this.databaseScopedConfigurations = other.databaseScopedConfigurations; } /// @@ -773,7 +780,8 @@ WHERE do.database_id = @DbID (this.queryStoreEnabled == other.queryStoreEnabled) && (this.maxSize == other.maxSize) && (this.backupStorageRedundancy == other.backupStorageRedundancy) && - (this.isLedger == other.isLedger); + (this.isLedger == other.isLedger) && + (this.databaseScopedConfigurations == other.databaseScopedConfigurations); return result; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype100.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype100.cs index 1a0f570e..ead09c66 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype100.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype100.cs @@ -199,51 +199,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { db.EncryptionEnabled = this.currentState.encryptionEnabled; } - - // Check if we support database scoped configurations in this database. Since these were all added at the same time, - // only check if MaxDop is supported rather than each individual property. - if (db.IsSupportedProperty("MaxDop")) - { - if (!this.Exists || (db.MaxDop != this.MaxDop)) - { - db.MaxDop = this.MaxDop; - } - - if (!this.Exists || (db.MaxDopForSecondary != this.MaxDopForSecondary)) - { - db.MaxDopForSecondary = this.MaxDopForSecondary; - } - - if (!this.Exists || (db.LegacyCardinalityEstimation != this.currentState.legacyCardinalityEstimation)) - { - db.LegacyCardinalityEstimation = this.currentState.legacyCardinalityEstimation; - } - - if (!this.Exists || (db.LegacyCardinalityEstimationForSecondary != this.currentState.legacyCardinalityEstimationForSecondary)) - { - db.LegacyCardinalityEstimationForSecondary = this.currentState.legacyCardinalityEstimationForSecondary; - } - - if (!this.Exists || (db.ParameterSniffing != this.currentState.parameterSniffing)) - { - db.ParameterSniffing = this.currentState.parameterSniffing; - } - - if (!this.Exists || (db.ParameterSniffingForSecondary != this.currentState.parameterSniffingForSecondary)) - { - db.ParameterSniffingForSecondary = this.currentState.parameterSniffingForSecondary; - } - - if (!this.Exists || (db.QueryOptimizerHotfixes != this.currentState.queryOptimizerHotfixes)) - { - db.QueryOptimizerHotfixes = this.currentState.queryOptimizerHotfixes; - } - - if (!this.Exists || (db.QueryOptimizerHotfixesForSecondary != this.currentState.queryOptimizerHotfixesForSecondary)) - { - db.QueryOptimizerHotfixesForSecondary = this.currentState.queryOptimizerHotfixesForSecondary; - } - } } #region Helper Methods diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs new file mode 100644 index 00000000..997b0487 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs @@ -0,0 +1,71 @@ +// +// 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.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Management; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database properties for SqlServer 2016 + /// + internal class DatabasePrototype130 : DatabasePrototype110 + { + // Properties that doen't support secondary value updates + // More info here: https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql?view=sql-server-ver16 + // The secondaryValUnsupportedPropsSet containst the configuration Ids of the below properties + // IDENTITY_CACHE(6) + // ELEVATE_ONLINE(11) + // ELEVATE_RESUMABLE(12) + // GLOBAL_TEMPORARY_TABLE_AUTO_DROP(21) + // PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES(25) + private static readonly HashSet secondaryValUnsupportedPropsSet = new HashSet { 6, 11, 12, 21, 25 }; + + /// + /// Database properties for SqlServer 2016 class constructor + /// + public DatabasePrototype130(CDataContainer context) + : base(context) + { + } + + [Category("Category_DatabaseScopedConfigurations")] + public DatabaseScopedConfigurationCollection DatabaseScopedConfiguration + { + get + { + return this.currentState.databaseScopedConfigurations; + } + set + { + this.currentState.databaseScopedConfigurations = value; + this.NotifyObservers(); + } + } + + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + + for (int i = 0; i < db.DatabaseScopedConfigurations.Count; i++) + { + if (db.DatabaseScopedConfigurations[i].Value != this.currentState.databaseScopedConfigurations[i].Value) + { + db.DatabaseScopedConfigurations[i].Value = this.currentState.databaseScopedConfigurations[i].Value; + } + + // Configurations that are not allowed secondary replicas are excluded. + if (db.DatabaseScopedConfigurations[i].ValueForSecondary != this.currentState.databaseScopedConfigurations[i].ValueForSecondary + && !secondaryValUnsupportedPropsSet.Contains(db.DatabaseScopedConfigurations[i].Id)) + { + db.DatabaseScopedConfigurations[i].ValueForSecondary = this.currentState.databaseScopedConfigurations[i].ValueForSecondary; + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs index 7b587f24..cb47e17f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs @@ -15,7 +15,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin /// /// Database properties for SqlServer 2017 /// - internal class DatabasePrototype140 : DatabasePrototype110 + internal class DatabasePrototype140 : DatabasePrototype130 { /// /// Database properties for SqlServer 2017 class constructor @@ -71,6 +71,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin } } } + + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs index df49c761..32487cbc 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs @@ -65,6 +65,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { this.prototype = new DatabasePrototype160(context); } + else if (majorVersionNumber >= 14) + { + this.prototype = new DatabasePrototype140(context); + } + else if (majorVersionNumber == 13) + { + this.prototype = new DatabasePrototype130(context); + } else if (Utils.IsSql11OrLater(context.Server.Version.Major)) { this.prototype = new DatabasePrototype110(context); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index b2478274..bd8364bb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -12301,14 +12301,6 @@ namespace Microsoft.SqlTools.ServiceLayer } } - public static string prototype_db_prop_databasescopedconfig_value_primary - { - get - { - return Keys.GetString(Keys.prototype_db_prop_databasescopedconfig_value_primary); - } - } - public static string error_db_prop_invalidleadingColumns { get @@ -17828,9 +17820,6 @@ namespace Microsoft.SqlTools.ServiceLayer public const string prototype_db_prop_databasescopedconfig_value_on = "prototype_db_prop_databasescopedconfig_value_on"; - public const string prototype_db_prop_databasescopedconfig_value_primary = "prototype_db_prop_databasescopedconfig_value_primary"; - - public const string error_db_prop_invalidleadingColumns = "error_db_prop_invalidleadingColumns"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 82dee144..c9dcdaa9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -6702,10 +6702,6 @@ The Query Processor estimates that implementing the following index could improv ON - - PRIMARY - - For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index edba298e..4c937614 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2786,7 +2786,6 @@ prototype_db_prop_varDecimalEnabled = VarDecimal Storage Format Enabled prototype_db_prop_encryptionEnabled = Encryption Enabled prototype_db_prop_databasescopedconfig_value_off = OFF prototype_db_prop_databasescopedconfig_value_on = ON -prototype_db_prop_databasescopedconfig_value_primary = PRIMARY error_db_prop_invalidleadingColumns = For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns compatibilityLevel_sphinx = SQL Server 7.0 (70) compatibilityLevel_shiloh = SQL Server 2000 (80) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index c1f895ef..d23596ff 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -8216,11 +8216,6 @@ The Query Processor estimates that implementing the following index could improv ON - - PRIMARY - PRIMARY - - For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs index 1136e591..564b1252 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs @@ -23,7 +23,7 @@ using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters; namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { - /// + /// /// Database object type handler /// public class DatabaseHandler : ObjectTypeHandler @@ -44,8 +44,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement internal static readonly string[] AzureEditionNames; internal static readonly string[] AzureBackupLevels; + internal static readonly string[] DscOnOffOptions; + internal static readonly string[] DscElevateOptions; + internal static readonly string[] DscEnableDisableOptions; internal static readonly AzureEditionDetails[] AzureMaxSizes; internal static readonly AzureEditionDetails[] AzureServiceLevels; + internal DatabaseScopedConfigurationCollection? databaseScopedConfigurationsCollection = null; static DatabaseHandler() { @@ -75,6 +79,22 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement displayRestrictAccessOptions.Add(DatabaseUserAccess.Single, SR.prototype_db_prop_restrictAccess_value_single); displayRestrictAccessOptions.Add(DatabaseUserAccess.Restricted, SR.prototype_db_prop_restrictAccess_value_restricted); + DscOnOffOptions = new[]{ + CommonConstants.DatabaseScopedConfigurations_Value_On, + CommonConstants.DatabaseScopedConfigurations_Value_Off + }; + + DscElevateOptions = new[]{ + CommonConstants.DatabaseScopedConfigurations_Value_Off, + CommonConstants.DatabaseScopedConfigurations_Value_When_supported, + CommonConstants.DatabaseScopedConfigurations_Value_Fail_Unsupported + }; + + DscEnableDisableOptions = new[]{ + CommonConstants.DatabaseScopedConfigurations_Value_Enabled, + CommonConstants.DatabaseScopedConfigurations_Value_Disabled + }; + // Set up maps from displayName to enum type so we can retrieve the equivalent enum types later when getting a Save/Script request. // We can't use a simple Enum.Parse for that since the displayNames get localized. foreach (CompatibilityLevel key in displayCompatLevels.Keys) @@ -160,7 +180,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement AutoShrink = smoDatabase.AutoShrink, AutoUpdateStatistics = smoDatabase.AutoUpdateStatisticsEnabled, AutoUpdateStatisticsAsynchronously = smoDatabase.AutoUpdateStatisticsAsync, - EncryptionEnabled = smoDatabase.EncryptionEnabled + EncryptionEnabled = smoDatabase.EncryptionEnabled, + DatabaseScopedConfigurations = smoDatabase.IsSupportedObject() ? GetDSCMetaData(smoDatabase.DatabaseScopedConfigurations) : null }; if (!isAzureDB) @@ -187,7 +208,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement ((DatabaseInfo)databaseViewInfo.ObjectInfo).IsLedgerDatabase = smoDatabase.IsLedger; } } + databaseScopedConfigurationsCollection = smoDatabase.IsSupportedObject() ? smoDatabase.DatabaseScopedConfigurations : null; } + databaseViewInfo.DscOnOffOptions = DscOnOffOptions; + databaseViewInfo.DscElevateOptions = DscElevateOptions; + databaseViewInfo.DscEnableDisableOptions = DscEnableDisableOptions; } // azure sql db doesn't have a sysadmin fixed role @@ -569,6 +594,36 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement db110.DatabaseContainmentType = containmentTypeEnums[database.ContainmentType]; } } + if (prototype is DatabasePrototype130 db130) + { + if (!viewParams.IsNewObject && databaseScopedConfigurationsCollection != null && database.DatabaseScopedConfigurations != null) + { + foreach (DatabaseScopedConfigurationsInfo dsc in database.DatabaseScopedConfigurations) + { + foreach (DatabaseScopedConfiguration smoDscCollection in databaseScopedConfigurationsCollection) + { + if (smoDscCollection.Name == dsc.Name) + { + smoDscCollection.Value = dsc.ValueForPrimary == CommonConstants.DatabaseScopedConfigurations_Value_Enabled + ? "1" : dsc.ValueForPrimary == CommonConstants.DatabaseScopedConfigurations_Value_Disabled + ? "0" : dsc.ValueForPrimary; + + // When sending the DSC seconday value to ADS, we convert the secondaryValue of 'PRIMARY' to match with primaryValue + // We need to set it back to 'PRIMARY' so that SMO would not generate any unnecessary scripts for unchanged properties + if (!(smoDscCollection.ValueForSecondary == CommonConstants.DatabaseScopedConfigurations_Value_Primary && + dsc.ValueForPrimary.Equals(dsc.ValueForSecondary))) + { + smoDscCollection.ValueForSecondary = dsc.ValueForSecondary == CommonConstants.DatabaseScopedConfigurations_Value_Enabled + ? "1" : dsc.ValueForSecondary == CommonConstants.DatabaseScopedConfigurations_Value_Disabled + ? "0" : dsc.ValueForSecondary; + } + break; + } + } + } + db130.DatabaseScopedConfiguration = databaseScopedConfigurationsCollection; + } + } // AutoCreateStatisticsIncremental can only be set when AutoCreateStatistics is enabled prototype.AutoCreateStatisticsIncremental = database.AutoCreateIncrementalStatistics; @@ -985,5 +1040,51 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } return sizes.ToArray(); } + + /// + /// Prepares database scoped configurations list + /// + /// + /// database scoped configurations metadata array + private static DatabaseScopedConfigurationsInfo[] GetDSCMetaData(DatabaseScopedConfigurationCollection smoDSCMetaData) + { + var dscMetaData = new List(); + foreach (DatabaseScopedConfiguration dsc in smoDSCMetaData) + { + string primaryValue = GetDscValue(dsc.Id, dsc.Value); + dscMetaData.Add(new DatabaseScopedConfigurationsInfo() + { + Id = dsc.Id, + Name = dsc.Name, + ValueForPrimary = primaryValue, + ValueForSecondary = dsc.ValueForSecondary == CommonConstants.DatabaseScopedConfigurations_Value_Primary ? primaryValue : GetDscValue(dsc.Id, dsc.ValueForSecondary) + }); + } + return dscMetaData.ToArray(); + } + + /// + /// Gets primary and secondary value of the database scoped configuration property + /// + /// + /// Value of the primary/secondary + private static string GetDscValue(int id, string value) + { + // MAXDOP(Id = 1) and PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES(Id = 25) are integer numbers but coming as string value type and they need to send as is. + if (id == 1 || id == 25) + { + return value; + } + + switch (value) + { + case "1": + return CommonConstants.DatabaseScopedConfigurations_Value_Enabled; + case "0": + return CommonConstants.DatabaseScopedConfigurations_Value_Disabled; + default: + return value; + } + } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs index 3efd2fbb..28e39ebb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs @@ -39,5 +39,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public bool? DatabaseReadOnly { get; set; } public bool EncryptionEnabled { get; set; } public string? RestrictAccess { get; set; } + public DatabaseScopedConfigurationsInfo[]? DatabaseScopedConfigurations { get; set; } + } + + public class DatabaseScopedConfigurationsInfo + { + public int Id { get; set; } + public string Name { get; set; } + public string ValueForPrimary { get; set; } + public string ValueForSecondary { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs index cddc2e3f..f8a36751 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs @@ -25,6 +25,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public AzureEditionDetails[] AzureMaxSizes { get; set; } public string[] PageVerifyOptions { get; set; } public string[] RestrictAccessOptions { get; set; } + public string[] DscOnOffOptions { get; set; } + public string[] DscElevateOptions { get; set; } + public string[] DscEnableDisableOptions { get; set; } } public class AzureEditionDetails diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/CommonConstants.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/CommonConstants.cs index f5a9fa39..630c3686 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/CommonConstants.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/CommonConstants.cs @@ -19,5 +19,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility public const string TempDbDatabaseName = "tempdb"; public const string DefaultBatchSeperator = "GO"; + + // Database scoped configurations property values + public const string DatabaseScopedConfigurations_Value_On = "ON"; + public const string DatabaseScopedConfigurations_Value_Off = "OFF"; + public const string DatabaseScopedConfigurations_Value_Primary = "PRIMARY"; + public const string DatabaseScopedConfigurations_Value_When_supported = "WHEN_SUPPORTED"; + public const string DatabaseScopedConfigurations_Value_Fail_Unsupported = "FAIL_UNSUPPORTED"; + public const string DatabaseScopedConfigurations_Value_Enabled = "ENABLED"; + public const string DatabaseScopedConfigurations_Value_Disabled = "DISABLED"; } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs index 2e380330..42c3a9c9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs @@ -300,6 +300,67 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).AutoUpdateStatisticsAsynchronously, Is.False, $"AutoUpdateStatisticsAsynchronously should match with testdata"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).PageVerify, Is.EqualTo(testDatabase.PageVerify), $"PageVerify should match with testdata"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).RestrictAccess, Is.EqualTo(testDatabase.RestrictAccess), $"RestrictAccess should match with testdata"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations, Is.Not.Null, $"DatabaseScopedConfigurations is not null"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations.Count, Is.GreaterThan(0), $"DatabaseScopedConfigurations should have at least a+ few properties"); + + // cleanup + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn, throwIfNotExist: true); + Assert.That(DatabaseExists(testDatabase.Name!, server), Is.False, $"Database '{testDatabase.Name}' was not dropped succesfully"); + } + finally + { + // Cleanup using SMO if Drop didn't work + DropDatabase(server, testDatabase.Name!); + } + } + } + + /// + /// Updating and validating database scoped configurations property values + /// + /// + [Test] + public async Task VerifyDatabaseScopedConfigurationsTest() + { + // setup, drop database if exists. + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", serverType: TestServerType.OnPrem); + using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo)) + { + var server = new Server(new ServerConnection(sqlConn)); + + var testDatabase = ObjectManagementTestUtils.GetTestDatabaseInfo(); + var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDatabase.Name); + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn); + + try + { + // create database + var parametersForCreation = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, "master", true, SqlObjectType.Database, "", ""); + await ObjectManagementTestUtils.SaveObject(parametersForCreation, testDatabase); + Assert.That(DatabaseExists(testDatabase.Name!, server), $"Expected database '{testDatabase.Name}' was not created succesfully"); + + // Get database properties and verify + var parametersForUpdate = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, testDatabase.Name, false, SqlObjectType.Database, "", objUrn); + DatabaseViewInfo databaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations, Is.Not.Null, $"DatabaseScopedConfigurations is not null"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations.Count, Is.GreaterThan(0), $"DatabaseScopedConfigurations should have at least a+ few properties"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForPrimary, Is.EqualTo("ON"), $"DatabaseScopedConfigurations primary value should match"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForSecondary, Is.EqualTo("ON"), $"DatabaseScopedConfigurations secondary value should match"); + + // Update few database scoped configurations + testDatabase.DatabaseScopedConfigurations = ((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations; + if (testDatabase.DatabaseScopedConfigurations.Length > 0) + { + // ACCELERATED_PLAN_FORCING + testDatabase.DatabaseScopedConfigurations[0].ValueForPrimary = "OFF"; + testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary = "OFF"; + } + await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabase); + DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase); + + // verify the modified properties + Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForPrimary, Is.EqualTo(testDatabase.DatabaseScopedConfigurations[0].ValueForPrimary), $"DSC updated primary value should match"); + Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForSecondary, Is.EqualTo(testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary), $"DSC updated Secondary value should match"); // cleanup await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn, throwIfNotExist: true); diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs index 3f10af9a..bb8f938c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs @@ -79,7 +79,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement AutoCreateStatistics = true, AutoShrink = false, AutoUpdateStatistics = true, - AutoUpdateStatisticsAsynchronously = false + AutoUpdateStatisticsAsynchronously = false, + DatabaseScopedConfigurations = null }; }