From a053457ba141167c2104a76364335ea43cf405b2 Mon Sep 17 00:00:00 2001 From: Sai Avishkar Sreerama <74571829+ssreerama@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:35:44 -0500 Subject: [PATCH] Enable QueryStore tab to the database properties (#2200) * initial commit * adding prototypefile and server conditions * Saving query store options completed, todo:tests * adding LOC stings according to the LOC version of the ms doc * removing common constants * merge conflict fix * fixing null reference exception * Adjusting the null reference exception property value in prototype.cs * removing unused directive * test fix that checks wrong value * Purge query store data changes * adding comment and uncommented the line --- .../Admin/Database/DatabasePrototype.cs | 22 +- .../Admin/Database/DatabasePrototype130.cs | 60 +++++ .../Admin/Database/DatabasePrototype140.cs | 11 + .../Admin/Database/DatabasePrototype150.cs | 55 +++++ .../Admin/Database/DatabasePrototype160.cs | 2 +- .../Admin/Database/DatabaseTaskHelper.cs | 6 +- .../Localization/sr.cs | 220 ++++++++++++++++++ .../Localization/sr.resx | 80 +++++++ .../Localization/sr.strings | 20 ++ .../Localization/sr.xlf | 100 ++++++++ .../Contracts/PurgeQueryStoreDataRequest.cs | 34 +++ .../ObjectManagementService.cs | 8 + .../ObjectTypes/Database/DatabaseHandler.cs | 133 +++++++++++ .../ObjectTypes/Database/DatabaseInfo.cs | 23 ++ .../ObjectTypes/Database/DatabaseViewInfo.cs | 5 + .../Utility/CommonConstants.cs | 3 + .../ObjectManagement/DatabaseHandlerTests.cs | 1 + .../ObjectManagementTestUtils.cs | 3 +- 18 files changed, 782 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype150.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/PurgeQueryStoreDataRequest.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs index 2ef3026f..327fc1b3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype.cs @@ -56,6 +56,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin public string currentServiceLevelObjective; public DbSize maxSize; public string backupStorageRedundancy; + public QueryStoreOptions queryStoreOptions; public bool closeCursorOnCommit; public bool isReadOnly; @@ -172,6 +173,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin this.queryOptimizerHotfixes = DatabaseScopedConfigurationOnOff.Off; this.queryOptimizerHotfixesForSecondary = DatabaseScopedConfigurationOnOff.Primary; this.isLedger = false; + this.queryStoreOptions = null; //The following properties are introduced for contained databases. //In case of plain old databases, these values should reflect the server configuration values. @@ -562,6 +564,22 @@ WHERE do.database_id = @DbID this.databaseScopedConfigurations = db.DatabaseScopedConfigurations; } + // Supported from Sql Server 2016 and higher + // QueryStore properties are not initialized at the time of database creation + if (db.IsSupportedObject()) + { + try + { + var queryStoreActualStateProperty = db.QueryStoreOptions.ActualState; + this.queryStoreOptions = db.QueryStoreOptions; + } + catch (NullReferenceException) + { + //db.QueryStoreOptions is not null, but its properties(actualState...etc) are Not initialized and when accessing them it throws the null reference exception. + this.queryStoreOptions = null; + } + } + //Only fill in the Azure properties when connected to an Azure server if (context.Server.ServerType == DatabaseEngineType.SqlAzureDatabase) { @@ -696,6 +714,7 @@ WHERE do.database_id = @DbID this.backupStorageRedundancy = other.backupStorageRedundancy; this.isLedger = other.isLedger; this.databaseScopedConfigurations = other.databaseScopedConfigurations; + this.queryStoreOptions = other.queryStoreOptions; } /// @@ -781,7 +800,8 @@ WHERE do.database_id = @DbID (this.maxSize == other.maxSize) && (this.backupStorageRedundancy == other.backupStorageRedundancy) && (this.isLedger == other.isLedger) && - (this.databaseScopedConfigurations == other.databaseScopedConfigurations); + (this.databaseScopedConfigurations == other.databaseScopedConfigurations) && + (this.queryStoreOptions == other.queryStoreOptions); return result; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs index 997b0487..075e33fb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs @@ -48,6 +48,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin } } + [Category("Category_QueryStoreOptions")] + public QueryStoreOptions QueryStoreOptions + { + get + { + return this.currentState.queryStoreOptions; + } + set + { + this.currentState.queryStoreOptions = value; + this.NotifyObservers(); + } + } + protected override void SaveProperties(Database db) { base.SaveProperties(db); @@ -66,6 +80,52 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin db.DatabaseScopedConfigurations[i].ValueForSecondary = this.currentState.databaseScopedConfigurations[i].ValueForSecondary; } } + + if (db.IsSupportedObject() && this.currentState.queryStoreOptions != null) + { + if (!this.Exists || (db.QueryStoreOptions.DesiredState != this.currentState.queryStoreOptions.DesiredState)) + { + db.QueryStoreOptions.DesiredState = this.currentState.queryStoreOptions.DesiredState; + } + + if (this.currentState.queryStoreOptions.DesiredState != QueryStoreOperationMode.Off) + { + if (!this.Exists || (db.QueryStoreOptions.DataFlushIntervalInSeconds != this.currentState.queryStoreOptions.DataFlushIntervalInSeconds)) + { + db.QueryStoreOptions.DataFlushIntervalInSeconds = this.currentState.queryStoreOptions.DataFlushIntervalInSeconds; + } + + if (!this.Exists || (db.QueryStoreOptions.StatisticsCollectionIntervalInMinutes != this.currentState.queryStoreOptions.StatisticsCollectionIntervalInMinutes)) + { + db.QueryStoreOptions.StatisticsCollectionIntervalInMinutes = this.currentState.queryStoreOptions.StatisticsCollectionIntervalInMinutes; + } + + if (!this.Exists || (db.QueryStoreOptions.MaxPlansPerQuery != this.currentState.queryStoreOptions.MaxPlansPerQuery)) + { + db.QueryStoreOptions.MaxPlansPerQuery = this.currentState.queryStoreOptions.MaxPlansPerQuery; + } + + if (!this.Exists || (db.QueryStoreOptions.MaxStorageSizeInMB != this.currentState.queryStoreOptions.MaxStorageSizeInMB)) + { + db.QueryStoreOptions.MaxStorageSizeInMB = this.currentState.queryStoreOptions.MaxStorageSizeInMB; + } + + if (!this.Exists || (db.QueryStoreOptions.QueryCaptureMode != this.currentState.queryStoreOptions.QueryCaptureMode)) + { + db.QueryStoreOptions.QueryCaptureMode = this.currentState.queryStoreOptions.QueryCaptureMode; + } + + if (!this.Exists || (db.QueryStoreOptions.SizeBasedCleanupMode != this.currentState.queryStoreOptions.SizeBasedCleanupMode)) + { + db.QueryStoreOptions.SizeBasedCleanupMode = this.currentState.queryStoreOptions.SizeBasedCleanupMode; + } + + if (!this.Exists || (db.QueryStoreOptions.StaleQueryThresholdInDays != this.currentState.queryStoreOptions.StaleQueryThresholdInDays)) + { + db.QueryStoreOptions.StaleQueryThresholdInDays = this.currentState.queryStoreOptions.StaleQueryThresholdInDays; + } + } + } } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs index cb47e17f..803ac215 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype140.cs @@ -75,6 +75,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin protected override void SaveProperties(Database db) { base.SaveProperties(db); + + if (db.IsSupportedObject() && this.currentState.queryStoreOptions != null) + { + if (this.currentState.queryStoreOptions.DesiredState != QueryStoreOperationMode.Off) + { + if (!this.Exists || (db.QueryStoreOptions.WaitStatsCaptureMode != this.currentState.queryStoreOptions.WaitStatsCaptureMode)) + { + db.QueryStoreOptions.WaitStatsCaptureMode = this.currentState.queryStoreOptions.WaitStatsCaptureMode; + } + } + } } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype150.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype150.cs new file mode 100644 index 00000000..ae68b5d4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype150.cs @@ -0,0 +1,55 @@ +// +// 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; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database properties for SqlServer 2019 + /// + internal class DatabasePrototype150 : DatabasePrototype140 + { + /// + /// Database properties for SqlServer 2019 class constructor + /// + public DatabasePrototype150(CDataContainer context) + : base(context) + { + } + + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + + if (db.IsSupportedObject() && this.currentState.queryStoreOptions != null) + { + if (this.currentState.queryStoreOptions.QueryCaptureMode == QueryStoreCaptureMode.Custom && this.currentState.queryStoreOptions.DesiredState != QueryStoreOperationMode.Off) + { + if (!this.Exists || (db.QueryStoreOptions.CapturePolicyExecutionCount != this.currentState.queryStoreOptions.CapturePolicyExecutionCount)) + { + db.QueryStoreOptions.CapturePolicyExecutionCount = this.currentState.queryStoreOptions.CapturePolicyExecutionCount; + } + + if (!this.Exists || (db.QueryStoreOptions.CapturePolicyStaleThresholdInHrs != this.currentState.queryStoreOptions.CapturePolicyStaleThresholdInHrs)) + { + db.QueryStoreOptions.CapturePolicyStaleThresholdInHrs = this.currentState.queryStoreOptions.CapturePolicyStaleThresholdInHrs; + } + + if (!this.Exists || (db.QueryStoreOptions.CapturePolicyTotalCompileCpuTimeInMS != this.currentState.queryStoreOptions.CapturePolicyTotalCompileCpuTimeInMS)) + { + db.QueryStoreOptions.CapturePolicyTotalCompileCpuTimeInMS = this.currentState.queryStoreOptions.CapturePolicyTotalCompileCpuTimeInMS; + } + + if (!this.Exists || (db.QueryStoreOptions.CapturePolicyTotalExecutionCpuTimeInMS != this.currentState.queryStoreOptions.CapturePolicyTotalExecutionCpuTimeInMS)) + { + db.QueryStoreOptions.CapturePolicyTotalExecutionCpuTimeInMS = this.currentState.queryStoreOptions.CapturePolicyTotalExecutionCpuTimeInMS; + } + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype160.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype160.cs index 276979ad..417b28bd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype160.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype160.cs @@ -12,7 +12,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin /// /// Database properties for SqlServer 2022 /// - internal class DatabasePrototype160 : DatabasePrototype140 + internal class DatabasePrototype160 : DatabasePrototype150 { /// /// Database properties for SqlServer 2022 class constructor diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs index 32487cbc..238104d7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabaseTaskHelper.cs @@ -65,7 +65,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { this.prototype = new DatabasePrototype160(context); } - else if (majorVersionNumber >= 14) + else if (majorVersionNumber == 15) + { + this.prototype = new DatabasePrototype150(context); + } + else if (majorVersionNumber == 14) { this.prototype = new DatabasePrototype140(context); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 352651f1..7f6d072f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -10709,6 +10709,166 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string querystorecapturemode_all + { + get + { + return Keys.GetString(Keys.querystorecapturemode_all); + } + } + + public static string querystorecapturemode_auto + { + get + { + return Keys.GetString(Keys.querystorecapturemode_auto); + } + } + + public static string querystorecapturemode_none + { + get + { + return Keys.GetString(Keys.querystorecapturemode_none); + } + } + + public static string querystorecapturemode_custom + { + get + { + return Keys.GetString(Keys.querystorecapturemode_custom); + } + } + + public static string queryStoreSizeBasedCleanupMode_Off + { + get + { + return Keys.GetString(Keys.queryStoreSizeBasedCleanupMode_Off); + } + } + + public static string queryStoreSizeBasedCleanupMode_Auto + { + get + { + return Keys.GetString(Keys.queryStoreSizeBasedCleanupMode_Auto); + } + } + + public static string statisticsCollectionInterval_OneMinute + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_OneMinute); + } + } + + public static string statisticsCollectionInterval_FiveMinutes + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_FiveMinutes); + } + } + + public static string statisticsCollectionInterval_TenMinutes + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_TenMinutes); + } + } + + public static string statisticsCollectionInterval_FifteenMinutes + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_FifteenMinutes); + } + } + + public static string statisticsCollectionInterval_ThirtyMinutes + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_ThirtyMinutes); + } + } + + public static string statisticsCollectionInterval_OneHour + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_OneHour); + } + } + + public static string statisticsCollectionInterval_OneDay + { + get + { + return Keys.GetString(Keys.statisticsCollectionInterval_OneDay); + } + } + + public static string queryStore_stale_threshold_OneHour + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_OneHour); + } + } + + public static string queryStore_stale_threshold_FourHours + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_FourHours); + } + } + + public static string queryStore_stale_threshold_EightHours + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_EightHours); + } + } + + public static string queryStore_stale_threshold_TwelveHours + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_TwelveHours); + } + } + + public static string queryStore_stale_threshold_OneDay + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_OneDay); + } + } + + public static string queryStore_stale_threshold_ThreeDays + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_ThreeDays); + } + } + + public static string queryStore_stale_threshold_SevenDays + { + get + { + return Keys.GetString(Keys.queryStore_stale_threshold_SevenDays); + } + } + public static string BasicAzureEdition { get @@ -15511,6 +15671,66 @@ namespace Microsoft.SqlTools.ServiceLayer public const string databaseBackupDate_None = "databaseBackupDate_None"; + public const string querystorecapturemode_all = "querystorecapturemode_all"; + + + public const string querystorecapturemode_auto = "querystorecapturemode_auto"; + + + public const string querystorecapturemode_none = "querystorecapturemode_none"; + + + public const string querystorecapturemode_custom = "querystorecapturemode_custom"; + + + public const string queryStoreSizeBasedCleanupMode_Off = "queryStoreSizeBasedCleanupMode_Off"; + + + public const string queryStoreSizeBasedCleanupMode_Auto = "queryStoreSizeBasedCleanupMode_Auto"; + + + public const string statisticsCollectionInterval_OneMinute = "statisticsCollectionInterval_OneMinute"; + + + public const string statisticsCollectionInterval_FiveMinutes = "statisticsCollectionInterval_FiveMinutes"; + + + public const string statisticsCollectionInterval_TenMinutes = "statisticsCollectionInterval_TenMinutes"; + + + public const string statisticsCollectionInterval_FifteenMinutes = "statisticsCollectionInterval_FifteenMinutes"; + + + public const string statisticsCollectionInterval_ThirtyMinutes = "statisticsCollectionInterval_ThirtyMinutes"; + + + public const string statisticsCollectionInterval_OneHour = "statisticsCollectionInterval_OneHour"; + + + public const string statisticsCollectionInterval_OneDay = "statisticsCollectionInterval_OneDay"; + + + public const string queryStore_stale_threshold_OneHour = "queryStore_stale_threshold_OneHour"; + + + public const string queryStore_stale_threshold_FourHours = "queryStore_stale_threshold_FourHours"; + + + public const string queryStore_stale_threshold_EightHours = "queryStore_stale_threshold_EightHours"; + + + public const string queryStore_stale_threshold_TwelveHours = "queryStore_stale_threshold_TwelveHours"; + + + public const string queryStore_stale_threshold_OneDay = "queryStore_stale_threshold_OneDay"; + + + public const string queryStore_stale_threshold_ThreeDays = "queryStore_stale_threshold_ThreeDays"; + + + public const string queryStore_stale_threshold_SevenDays = "queryStore_stale_threshold_SevenDays"; + + public const string BasicAzureEdition = "BasicAzureEdition"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index fb7e9c00..b98ab7b9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -5911,6 +5911,86 @@ The Query Processor estimates that implementing the following index could improv None + + All + + + + Auto + + + + None + + + + Custom + + + + Off + + + + Auto + + + + 1 Minute + + + + 5 Minutes + + + + 10 Minutes + + + + 15 Minutes + + + + 30 Minutes + + + + 1 Hour + + + + 1 Day + + + + 1 Hour + + + + 4 Hours + + + + 8 Hours + + + + 12 Hours + + + + 1 Day + + + + 3 Days + + + + 7 Days + + Basic diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 7add8bfb..7a283440 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2390,6 +2390,26 @@ general_containmentType_Partial = Partial filegroups_filestreamFiles = FILESTREAM Files prototype_file_noApplicableFileGroup = No Applicable Filegroup databaseBackupDate_None = None +querystorecapturemode_all = All +querystorecapturemode_auto = Auto +querystorecapturemode_none = None +querystorecapturemode_custom = Custom +queryStoreSizeBasedCleanupMode_Off = Off +queryStoreSizeBasedCleanupMode_Auto = Auto +statisticsCollectionInterval_OneMinute = 1 Minute +statisticsCollectionInterval_FiveMinutes = 5 Minutes +statisticsCollectionInterval_TenMinutes = 10 Minutes +statisticsCollectionInterval_FifteenMinutes = 15 Minutes +statisticsCollectionInterval_ThirtyMinutes = 30 Minutes +statisticsCollectionInterval_OneHour = 1 Hour +statisticsCollectionInterval_OneDay = 1 Day +queryStore_stale_threshold_OneHour = 1 Hour +queryStore_stale_threshold_FourHours = 4 Hours +queryStore_stale_threshold_EightHours = 8 Hours +queryStore_stale_threshold_TwelveHours = 12 Hours +queryStore_stale_threshold_OneDay = 1 Day +queryStore_stale_threshold_ThreeDays = 3 Days +queryStore_stale_threshold_SevenDays = 7 Days ############################################################################ # Azure SQL DB diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 89efe848..cd93d006 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -7264,6 +7264,106 @@ The Query Processor estimates that implementing the following index could improv Failed to find connection info about the server + + All + All + + + + Auto + Auto + + + + None + None + + + + Custom + Custom + + + + Off + Off + + + + Auto + Auto + + + + 1 Minute + 1 Minute + + + + 5 Minutes + 5 Minutes + + + + 10 Minutes + 10 Minutes + + + + 15 Minutes + 15 Minutes + + + + 30 Minutes + 30 Minutes + + + + 1 Hour + 1 Hour + + + + 1 Day + 1 Day + + + + 1 Hour + 1 Hour + + + + 4 Hours + 4 Hours + + + + 8 Hours + 8 Hours + + + + 12 Hours + 12 Hours + + + + 1 Day + 1 Day + + + + 3 Days + 3 Days + + + + 7 Days + 7 Days + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/PurgeQueryStoreDataRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/PurgeQueryStoreDataRequest.cs new file mode 100644 index 00000000..968a59ab --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/PurgeQueryStoreDataRequest.cs @@ -0,0 +1,34 @@ +// +// 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.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts +{ + public class PurgeQueryStoreDataRequestParams : GeneralRequestDetails + { + /// + /// SFC (SMO) URN identifying the object + /// + public string ObjectUrn { get; set; } + /// + /// The target database name. + /// + public string Database { get; set; } + /// + /// URI of the underlying connection for this request + /// + public string ConnectionUri { get; set; } + } + + public class PurgeQueryStoreDataRequestResponse { } + + public class PurgeQueryStoreDataRequest + { + public static readonly RequestType Type = RequestType.Create("objectManagement/purgeQueryStoreData"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs index 9228c7f5..c69b63a0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs @@ -72,6 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement this.serviceHost.SetRequestHandler(DetachDatabaseRequest.Type, HandleDetachDatabaseRequest, true); this.serviceHost.SetRequestHandler(AttachDatabaseRequest.Type, HandleAttachDatabaseRequest, true); this.serviceHost.SetRequestHandler(DropDatabaseRequest.Type, HandleDropDatabaseRequest, true); + this.serviceHost.SetRequestHandler(PurgeQueryStoreDataRequest.Type, HandlePurgeQueryStoreDataRequest, true); } internal async Task HandleRenameRequest(RenameRequestParams requestParams, RequestContext requestContext) @@ -222,6 +223,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement await requestContext.SendResult(sqlScript); } + internal async Task HandlePurgeQueryStoreDataRequest(PurgeQueryStoreDataRequestParams requestParams, RequestContext requestContext) + { + var handler = this.GetObjectTypeHandler(SqlObjectType.Database) as DatabaseHandler; + handler.PurgeQueryStoreData(requestParams); + await requestContext.SendResult(new PurgeQueryStoreDataRequestResponse()); + } + private IObjectTypeHandler GetObjectTypeHandler(SqlObjectType objectType) { foreach (var handler in objectTypeHandlers) diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs index 346fde23..20dd1c2a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs @@ -42,11 +42,20 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement private static readonly Dictionary displayPageVerifyOptions = new Dictionary(); private static readonly Dictionary displayRestrictAccessOptions = new Dictionary(); private static readonly ConcurrentDictionary displayFileTypes = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary displayOperationModeOptions = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary displayQueryStoreCaptureModeOptions = new ConcurrentDictionary(); + private static readonly SortedDictionary displayStatisticsCollectionIntervalInMinutes = new SortedDictionary(); + private static readonly SortedDictionary displayQueryStoreStaleThresholdInHours = new SortedDictionary(); + private static readonly ConcurrentDictionary displaySizeBasedCleanupMode = new ConcurrentDictionary(); private static readonly Dictionary compatLevelEnums = new Dictionary(); private static readonly Dictionary containmentTypeEnums = new Dictionary(); private static readonly Dictionary recoveryModelEnums = new Dictionary(); private static readonly Dictionary fileTypesEnums = new Dictionary(); + private static readonly Dictionary operationModeEnums = new Dictionary(); + private static readonly Dictionary captureModeEnums = new Dictionary(); + private static readonly Dictionary statisticsCollectionIntervalValues = new Dictionary(); + private static readonly Dictionary queryStoreStaleThresholdValues = new Dictionary(); internal static readonly string[] AzureEditionNames; internal static readonly string[] AzureBackupLevels; @@ -89,6 +98,33 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement displayFileTypes.TryAdd(FileType.Log, SR.prototype_file_logFile); displayFileTypes.TryAdd(FileType.FileStream, SR.prototype_file_filestreamFile); + displayOperationModeOptions.TryAdd(QueryStoreOperationMode.Off, CommonConstants.QueryStoreOperationMode_Off); + displayOperationModeOptions.TryAdd(QueryStoreOperationMode.ReadOnly, CommonConstants.QueryStoreOperationMode_ReadOnly); + displayOperationModeOptions.TryAdd(QueryStoreOperationMode.ReadWrite, CommonConstants.QueryStoreOperationMode_ReadWrite); + + displayQueryStoreCaptureModeOptions.TryAdd(QueryStoreCaptureMode.All, SR.querystorecapturemode_all); + displayQueryStoreCaptureModeOptions.TryAdd(QueryStoreCaptureMode.Auto, SR.querystorecapturemode_auto); + displayQueryStoreCaptureModeOptions.TryAdd(QueryStoreCaptureMode.None, SR.querystorecapturemode_none); + + displayStatisticsCollectionIntervalInMinutes.TryAdd(1, SR.statisticsCollectionInterval_OneMinute); + displayStatisticsCollectionIntervalInMinutes.TryAdd(5, SR.statisticsCollectionInterval_FiveMinutes); + displayStatisticsCollectionIntervalInMinutes.TryAdd(10, SR.statisticsCollectionInterval_TenMinutes); + displayStatisticsCollectionIntervalInMinutes.TryAdd(15, SR.statisticsCollectionInterval_FifteenMinutes); + displayStatisticsCollectionIntervalInMinutes.TryAdd(30, SR.statisticsCollectionInterval_ThirtyMinutes); + displayStatisticsCollectionIntervalInMinutes.TryAdd(60, SR.statisticsCollectionInterval_OneHour); + displayStatisticsCollectionIntervalInMinutes.TryAdd(1440, SR.statisticsCollectionInterval_OneDay); + + displayQueryStoreStaleThresholdInHours.TryAdd(1, SR.queryStore_stale_threshold_OneHour); + displayQueryStoreStaleThresholdInHours.TryAdd(4, SR.queryStore_stale_threshold_FourHours); + displayQueryStoreStaleThresholdInHours.TryAdd(8, SR.queryStore_stale_threshold_EightHours); + displayQueryStoreStaleThresholdInHours.TryAdd(12, SR.queryStore_stale_threshold_TwelveHours); + displayQueryStoreStaleThresholdInHours.TryAdd(24, SR.queryStore_stale_threshold_OneDay); + displayQueryStoreStaleThresholdInHours.TryAdd(72, SR.queryStore_stale_threshold_ThreeDays); + displayQueryStoreStaleThresholdInHours.TryAdd(168, SR.queryStore_stale_threshold_SevenDays); + + displaySizeBasedCleanupMode.TryAdd(QueryStoreSizeBasedCleanupMode.Off, SR.queryStoreSizeBasedCleanupMode_Off); + displaySizeBasedCleanupMode.TryAdd(QueryStoreSizeBasedCleanupMode.Auto, SR.queryStoreSizeBasedCleanupMode_Auto); + DscOnOffOptions = new[]{ CommonConstants.DatabaseScopedConfigurations_Value_On, CommonConstants.DatabaseScopedConfigurations_Value_Off @@ -123,6 +159,22 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { fileTypesEnums.Add(displayFileTypes[key], key); } + foreach (QueryStoreOperationMode key in displayOperationModeOptions.Keys) + { + operationModeEnums.Add(displayOperationModeOptions[key], key); + } + foreach (QueryStoreCaptureMode key in displayQueryStoreCaptureModeOptions.Keys) + { + captureModeEnums.Add(displayQueryStoreCaptureModeOptions[key], key); + } + foreach (KeyValuePair pair in displayStatisticsCollectionIntervalInMinutes) + { + statisticsCollectionIntervalValues.Add(pair.Value, pair.Key); + } + foreach (KeyValuePair pair in displayQueryStoreStaleThresholdInHours) + { + queryStoreStaleThresholdValues.Add(pair.Value, pair.Key); + } // Azure SLO info is invariant of server information, so set up static objects we can return later var editions = AzureSqlDbHelper.GetValidAzureEditionOptions(); @@ -202,6 +254,38 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement ((DatabaseInfo)databaseViewInfo.ObjectInfo).RecoveryModel = displayRecoveryModels[smoDatabase.RecoveryModel]; ((DatabaseInfo)databaseViewInfo.ObjectInfo).LastDatabaseBackup = smoDatabase.LastBackupDate == DateTime.MinValue ? SR.databaseBackupDate_None : smoDatabase.LastBackupDate.ToString(); ((DatabaseInfo)databaseViewInfo.ObjectInfo).LastDatabaseLogBackup = smoDatabase.LastLogBackupDate == DateTime.MinValue ? SR.databaseBackupDate_None : smoDatabase.LastLogBackupDate.ToString(); + if (prototype is DatabasePrototype130) + { + ((DatabaseInfo)databaseViewInfo.ObjectInfo).QueryStoreOptions = new QueryStoreOptions() + { + ActualMode = displayOperationModeOptions[smoDatabase.QueryStoreOptions.ActualState], + DataFlushIntervalInMinutes = smoDatabase.QueryStoreOptions.DataFlushIntervalInSeconds / 60, + StatisticsCollectionInterval = displayStatisticsCollectionIntervalInMinutes[(int)smoDatabase.QueryStoreOptions.StatisticsCollectionIntervalInMinutes], + MaxPlansPerQuery = smoDatabase.QueryStoreOptions.MaxPlansPerQuery, + MaxSizeInMB = smoDatabase.QueryStoreOptions.MaxStorageSizeInMB, + QueryStoreCaptureMode = smoDatabase.QueryStoreOptions.QueryCaptureMode.ToString(), + SizeBasedCleanupMode = smoDatabase.QueryStoreOptions.SizeBasedCleanupMode.ToString(), + StaleQueryThresholdInDays = smoDatabase.QueryStoreOptions.StaleQueryThresholdInDays, + CurrentStorageSizeInMB = smoDatabase.QueryStoreOptions.CurrentStorageSizeInMB + }; + if (prototype is DatabasePrototype140) + { + ((DatabaseInfo)databaseViewInfo.ObjectInfo).QueryStoreOptions!.WaitStatisticsCaptureMode = smoDatabase.QueryStoreOptions.WaitStatsCaptureMode == QueryStoreWaitStatsCaptureMode.On; + }; + if (prototype is DatabasePrototype150) + { + ((DatabaseInfo)databaseViewInfo.ObjectInfo).QueryStoreOptions!.CapturePolicyOptions = new QueryStoreCapturePolicyOptions() + { + ExecutionCount = smoDatabase.QueryStoreOptions.CapturePolicyExecutionCount, + StaleThreshold = displayQueryStoreStaleThresholdInHours[(int)smoDatabase.QueryStoreOptions.CapturePolicyStaleThresholdInHrs], + TotalCompileCPUTimeInMS = smoDatabase.QueryStoreOptions.CapturePolicyTotalCompileCpuTimeInMS, + TotalExecutionCPUTimeInMS = smoDatabase.QueryStoreOptions.CapturePolicyTotalExecutionCpuTimeInMS + }; + + // Sql Server 2019 and higher only support the custom query store capture mode + displayQueryStoreCaptureModeOptions.TryAdd(QueryStoreCaptureMode.Custom, SR.querystorecapturemode_custom); + } + } } if (!isManagedInstance) { @@ -229,6 +313,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement databaseViewInfo.DscOnOffOptions = DscOnOffOptions; databaseViewInfo.DscElevateOptions = DscElevateOptions; databaseViewInfo.DscEnableDisableOptions = DscEnableDisableOptions; + databaseViewInfo.OperationModeOptions = displayOperationModeOptions.Values.ToArray(); + databaseViewInfo.QueryStoreCaptureModeOptions = displayQueryStoreCaptureModeOptions.Values.ToArray(); + databaseViewInfo.StatisticsCollectionIntervalOptions = displayStatisticsCollectionIntervalInMinutes.Values.ToArray(); + databaseViewInfo.StaleThresholdOptions = displayQueryStoreStaleThresholdInHours.Values.ToArray(); + databaseViewInfo.SizeBasedCleanupModeOptions = displaySizeBasedCleanupMode.Values.ToArray(); } // azure sql db doesn't have a sysadmin fixed role @@ -541,6 +630,22 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement return sqlScript; } + /// + /// Clears all query store data from the database + /// + /// + public void PurgeQueryStoreData(PurgeQueryStoreDataRequestParams purgeParams) + { + using (var dataContainer = CreateDatabaseDataContainer(purgeParams.ConnectionUri, purgeParams.ObjectUrn, false, purgeParams.Database)) + { + var smoDatabase = dataContainer.SqlDialogSubject as Database; + if (smoDatabase != null) + { + smoDatabase.QueryStoreOptions.PurgeQueryStoreData(); + } + } + } + private CDataContainer CreateDatabaseDataContainer(string connectionUri, string? objectURN, bool isNewDatabase, string? databaseName) { ConnectionInfo connectionInfo = this.GetConnectionInfo(connectionUri); @@ -684,6 +789,34 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } db130.DatabaseScopedConfiguration = databaseScopedConfigurationsCollection; } + + if (!viewParams.IsNewObject && database.QueryStoreOptions != null) + { + // Sql Server 2019 and higher supports custom query store capture mode + captureModeEnums.TryAdd(SR.querystorecapturemode_custom, QueryStoreCaptureMode.Custom); + db130.QueryStoreOptions.DesiredState = operationModeEnums[database.QueryStoreOptions.ActualMode]; + db130.QueryStoreOptions.DataFlushIntervalInSeconds = database.QueryStoreOptions.DataFlushIntervalInMinutes * 60; + db130.QueryStoreOptions.StatisticsCollectionIntervalInMinutes = statisticsCollectionIntervalValues[database.QueryStoreOptions.StatisticsCollectionInterval]; + db130.QueryStoreOptions.MaxPlansPerQuery = database.QueryStoreOptions.MaxPlansPerQuery; + db130.QueryStoreOptions.MaxStorageSizeInMB = database.QueryStoreOptions.MaxSizeInMB; + db130.QueryStoreOptions.QueryCaptureMode = captureModeEnums[database.QueryStoreOptions.QueryStoreCaptureMode]; + db130.QueryStoreOptions.SizeBasedCleanupMode = database.QueryStoreOptions.SizeBasedCleanupMode == SR.queryStoreSizeBasedCleanupMode_Off ? QueryStoreSizeBasedCleanupMode.Off : QueryStoreSizeBasedCleanupMode.Auto; + db130.QueryStoreOptions.StaleQueryThresholdInDays = database.QueryStoreOptions.StaleQueryThresholdInDays; + if (prototype is DatabasePrototype140 db140 && database.QueryStoreOptions.WaitStatisticsCaptureMode != null) + { + db140.QueryStoreOptions.WaitStatsCaptureMode = (bool)database.QueryStoreOptions.WaitStatisticsCaptureMode ? QueryStoreWaitStatsCaptureMode.On : QueryStoreWaitStatsCaptureMode.Off; + } + + if (prototype is DatabasePrototype150 db150 && database.QueryStoreOptions.QueryStoreCaptureMode == SR.querystorecapturemode_custom + && database.QueryStoreOptions.CapturePolicyOptions != null) + { + db150.QueryStoreOptions.CapturePolicyExecutionCount = database.QueryStoreOptions.CapturePolicyOptions.ExecutionCount; + db150.QueryStoreOptions.CapturePolicyStaleThresholdInHrs = queryStoreStaleThresholdValues[database.QueryStoreOptions.CapturePolicyOptions.StaleThreshold]; + db150.QueryStoreOptions.CapturePolicyTotalCompileCpuTimeInMS = database.QueryStoreOptions.CapturePolicyOptions.TotalCompileCPUTimeInMS; + db150.QueryStoreOptions.CapturePolicyTotalExecutionCpuTimeInMS = database.QueryStoreOptions.CapturePolicyOptions.TotalCompileCPUTimeInMS; + + } + } } if (!viewParams.IsNewObject && database.Files != null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs index 75df8fed..cb37316e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs @@ -45,6 +45,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public bool? IsFilesTabSupported { get; set; } public DatabaseFile[] Files { get; set; } public FileGroupSummary[]? Filegroups { get; set; } + public QueryStoreOptions? QueryStoreOptions { get; set; } } public class DatabaseScopedConfigurationsInfo @@ -80,4 +81,26 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public bool AutogrowAllFiles { get; set; } } + public class QueryStoreOptions + { + public string ActualMode { get; set; } + public long DataFlushIntervalInMinutes { get; set; } + public string StatisticsCollectionInterval { get; set; } + public long MaxPlansPerQuery { get; set; } + public long MaxSizeInMB { get; set; } + public string QueryStoreCaptureMode { get; set; } + public string SizeBasedCleanupMode { get; set; } + public long StaleQueryThresholdInDays { get; set; } + public bool? WaitStatisticsCaptureMode { get; set; } + public QueryStoreCapturePolicyOptions? CapturePolicyOptions { get; set; } + public long CurrentStorageSizeInMB { get; set; } + } + + public class QueryStoreCapturePolicyOptions + { + public int ExecutionCount { get; set; } + public string StaleThreshold { get; set; } + public long TotalCompileCPUTimeInMS { get; set; } + public long TotalExecutionCPUTimeInMS { 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 6595eaca..0a3be48e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs @@ -28,6 +28,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public string[] DscElevateOptions { get; set; } public string[] DscEnableDisableOptions { get; set; } public string[] FileTypesOptions { get; set; } + public string[] OperationModeOptions { get; set; } + public string[] StatisticsCollectionIntervalOptions { get; set; } + public string[] QueryStoreCaptureModeOptions { get; set; } + public string[] SizeBasedCleanupModeOptions { get; set; } + public string[] StaleThresholdOptions { get; set; } } public class AzureEditionDetails diff --git a/src/Microsoft.SqlTools.SqlCore/Utility/CommonConstants.cs b/src/Microsoft.SqlTools.SqlCore/Utility/CommonConstants.cs index d00a0334..d2f753d2 100644 --- a/src/Microsoft.SqlTools.SqlCore/Utility/CommonConstants.cs +++ b/src/Microsoft.SqlTools.SqlCore/Utility/CommonConstants.cs @@ -25,5 +25,8 @@ namespace Microsoft.SqlTools.SqlCore.Utility public const string DatabaseScopedConfigurations_Value_Fail_Unsupported = "FAIL_UNSUPPORTED"; public const string DatabaseScopedConfigurations_Value_Enabled = "ENABLED"; public const string DatabaseScopedConfigurations_Value_Disabled = "DISABLED"; + public const string QueryStoreOperationMode_Off = "Off"; + public const string QueryStoreOperationMode_ReadOnly = "Read Only"; + public const string QueryStoreOperationMode_ReadWrite = "Read Write"; } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs index b829b3ef..0892a90c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs @@ -311,6 +311,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[0].Type, Is.EqualTo("ROWS Data"), $"Database files first file should be Row type database files"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[1].Type, Is.EqualTo("LOG"), $"Database files first file should be Log type database files"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Filegroups?.Length, Is.GreaterThan(0), $"Database file groups should exists"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).QueryStoreOptions, Is.Not.Null, $"Database Query store options are not null"); // 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 215cb5b6..082818d4 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs @@ -83,7 +83,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement AutoShrink = false, AutoUpdateStatistics = true, AutoUpdateStatisticsAsynchronously = false, - DatabaseScopedConfigurations = null + DatabaseScopedConfigurations = null, + QueryStoreOptions = null }; }