diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 73913c15..89df9978 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -2381,6 +2381,134 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string FilterName + { + get + { + return Keys.GetString(Keys.FilterName); + } + } + + public static string FilterNameDescription + { + get + { + return Keys.GetString(Keys.FilterNameDescription); + } + } + + public static string FilterSchema + { + get + { + return Keys.GetString(Keys.FilterSchema); + } + } + + public static string FilterSchemaDescription + { + get + { + return Keys.GetString(Keys.FilterSchemaDescription); + } + } + + public static string FilterOwner + { + get + { + return Keys.GetString(Keys.FilterOwner); + } + } + + public static string FilterOwnerDescription + { + get + { + return Keys.GetString(Keys.FilterOwnerDescription); + } + } + + public static string FilterDurabilityType + { + get + { + return Keys.GetString(Keys.FilterDurabilityType); + } + } + + public static string FilterDurabilityTypeDescription + { + get + { + return Keys.GetString(Keys.FilterDurabilityTypeDescription); + } + } + + public static string FilterIsMemoryOptimized + { + get + { + return Keys.GetString(Keys.FilterIsMemoryOptimized); + } + } + + public static string FilterIsMemoryOptimizedDescription + { + get + { + return Keys.GetString(Keys.FilterIsMemoryOptimizedDescription); + } + } + + public static string FilterCreateDate + { + get + { + return Keys.GetString(Keys.FilterCreateDate); + } + } + + public static string FilterCreateDateDescription + { + get + { + return Keys.GetString(Keys.FilterCreateDateDescription); + } + } + + public static string FilterIsNativelyCompiled + { + get + { + return Keys.GetString(Keys.FilterIsNativelyCompiled); + } + } + + public static string FilterIsNativelyCompiledDescription + { + get + { + return Keys.GetString(Keys.FilterIsNativelyCompiledDescription); + } + } + + public static string FilterInPrimaryKey + { + get + { + return Keys.GetString(Keys.FilterInPrimaryKey); + } + } + + public static string FilterInPrimaryKeyDescription + { + get + { + return Keys.GetString(Keys.FilterInPrimaryKeyDescription); + } + } + public static string ScriptingParams_ConnectionString_Property_Invalid { get @@ -12045,6 +12173,54 @@ namespace Microsoft.SqlTools.ServiceLayer public const string DatabaseNotAccessible = "DatabaseNotAccessible"; + public const string FilterName = "FilterName"; + + + public const string FilterNameDescription = "FilterNameDescription"; + + + public const string FilterSchema = "FilterSchema"; + + + public const string FilterSchemaDescription = "FilterSchemaDescription"; + + + public const string FilterOwner = "FilterOwner"; + + + public const string FilterOwnerDescription = "FilterOwnerDescription"; + + + public const string FilterDurabilityType = "FilterDurabilityType"; + + + public const string FilterDurabilityTypeDescription = "FilterDurabilityTypeDescription"; + + + public const string FilterIsMemoryOptimized = "FilterIsMemoryOptimized"; + + + public const string FilterIsMemoryOptimizedDescription = "FilterIsMemoryOptimizedDescription"; + + + public const string FilterCreateDate = "FilterCreateDate"; + + + public const string FilterCreateDateDescription = "FilterCreateDateDescription"; + + + public const string FilterIsNativelyCompiled = "FilterIsNativelyCompiled"; + + + public const string FilterIsNativelyCompiledDescription = "FilterIsNativelyCompiledDescription"; + + + public const string FilterInPrimaryKey = "FilterInPrimaryKey"; + + + public const string FilterInPrimaryKeyDescription = "FilterInPrimaryKeyDescription"; + + public const string ScriptingParams_ConnectionString_Property_Invalid = "ScriptingParams_ConnectionString_Property_Invalid"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 26d974d8..959cad24 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -1458,6 +1458,70 @@ The database {0} is not accessible. + + Name + + + + Include or exclude objects based on the name or part of a name. + + + + Schema + + + + Include or exclude objects based on the schema or part of a schema name. + + + + Owner + + + + Include or exclude objects based on the owner or part of an owner name. + + + + Durability Type + + + + Include or exclude objects based on the durability type. + + + + Is Memory Optimized + + + + Include or exclude objects based on whether the object is memory optimized. + + + + Create Date + + + + Select or type a creation date to include or exclude objects created at any time on that date, or enter a starting and ending date to include or exclude objects created in that inclusive date range. + + + + Is Natively Compiled + + + + Include or exclude objects based on whether the object is natively compiled. + + + + In Primary Key + + + + Include or exclude objects based on whether the column is in a primary key. + + Error parsing ScriptingParams.ConnectionString property. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index aa2925ab..ad4f9798 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -720,6 +720,22 @@ FileTable_LabelPart = File Table DatabaseNotAccessible = The database {0} is not accessible. +FilterName = Name +FilterNameDescription = Include or exclude objects based on the name or part of a name. +FilterSchema = Schema +FilterSchemaDescription = Include or exclude objects based on the schema or part of a schema name. +FilterOwner = Owner +FilterOwnerDescription = Include or exclude objects based on the owner or part of an owner name. +FilterDurabilityType = Durability Type +FilterDurabilityTypeDescription = Include or exclude objects based on the durability type. +FilterIsMemoryOptimized = Is Memory Optimized +FilterIsMemoryOptimizedDescription = Include or exclude objects based on whether the object is memory optimized. +FilterCreateDate = Create Date +FilterCreateDateDescription = Select or type a creation date to include or exclude objects created at any time on that date, or enter a starting and ending date to include or exclude objects created in that inclusive date range. +FilterIsNativelyCompiled = Is Natively Compiled +FilterIsNativelyCompiledDescription = Include or exclude objects based on whether the object is natively compiled. +FilterInPrimaryKey = In Primary Key +FilterInPrimaryKeyDescription = Include or exclude objects based on whether the column is in a primary key. ############################################################################ # Scripting Service diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 8aaba375..06996488 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -7137,6 +7137,76 @@ The Query Processor estimates that implementing the following index could improv Impersonate Any Login + + Name + Name + + + + Include or exclude objects based on the name or part of a name. + Include or exclude objects based on the name or part of a name. + + + + Schema + Schema + + + + Include or exclude objects based on the schema or part of a schema name. + Include or exclude objects based on the schema or part of a schema name. + + + + Owner + Owner + + + + Include or exclude objects based on the owner or part of an owner name. + Include or exclude objects based on the owner or part of an owner name. + + + + Durability Type + Durability Type + + + + Include or exclude objects based on the durability type. + Include or exclude objects based on the durability type. + + + + Is Memory Optimized + Is Memory Optimized + + + + Include or exclude objects based on whether the object is memory optimized. + Include or exclude objects based on whether the object is memory optimized. + + + + Create Date + Create Date + + + + Select or type a creation date to include or exclude objects created at any time on that date, or enter a starting and ending date to include or exclude objects created in that inclusive date range. + Select or type a creation date to include or exclude objects created at any time on that date, or enter a starting and ending date to include or exclude objects created in that inclusive date range. + + + + Is Natively Compiled + Is Natively Compiled + + + + Include or exclude objects based on whether the object is natively compiled. + Include or exclude objects based on whether the object is natively compiled. + + SetServiceProvider() was not called to establish the required service provider SetServiceProvider() was not called to establish the required service provider @@ -7147,6 +7217,16 @@ The Query Processor estimates that implementing the following index could improv Service {0} was not found in the service provider + + In Primary Key + In Primary Key + + + + Include or exclude objects based on whether the column is in a primary key. + Include or exclude objects based on whether the column is in a primary key. + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs index 0afc2144..933765e9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs @@ -57,6 +57,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts /// Security token for AzureMFA authentication for refresing access token on connection. /// public SecurityToken? SecurityToken { get; set; } + + /// + /// Filters to apply to the expand request + /// + public NodeFilter[]? Filters { get; set; } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs index ba5a879b..fedbcdb2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs @@ -6,6 +6,7 @@ #nullable disable using Microsoft.SqlTools.ServiceLayer.Metadata.Contracts; +using Newtonsoft.Json.Linq; namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts { @@ -71,5 +72,86 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts /// The object type of the node. e.g. Database, Server, Tables... /// public string ObjectType { get; set; } + /// + /// Filterable properties that this node supports + /// + public NodeFilterProperty[] FilterableProperties { get; set; } + } + + /// + /// The filterable properties that a node supports + /// + public class NodeFilterProperty + { + /// + /// The name of the filter property + /// + public string Name { get; set; } + /// + /// The name of the filter property displayed to the user + /// + public string DisplayName { get; set; } + /// + /// The description of the filter property + /// + public string Description { get; set; } + /// + /// The data type of the filter property + /// + public NodeFilterPropertyDataType Type { get; set; } + /// + /// The list of choices for the filter property if the type is choice + /// + public string[] Choices { get; set; } + } + + /// + /// The data type of the filter property. Matches NodeFilterPropertyDataType enum in ADS : https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.proposed.d.ts#L1847-L1853 + /// + public enum NodeFilterPropertyDataType + { + String = 0, + Number = 1, + Boolean = 2, + Date = 3, + Choice = 4 + } + + /// + /// The operator of the filter property. Matches NodeFilterOperator enum in ADS: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.proposed.d.ts#L1855-L1868 + /// + public enum NodeFilterOperator + { + Equals = 0, + NotEquals = 1, + LessThan = 2, + LessThanOrEquals = 3, + GreaterThan = 4, + GreaterThanOrEquals = 5, + Between = 6, + NotBetween = 7, + Contains = 8, + NotContains = 9, + } + + /// + /// The filters that can be used to filter the nodes in an expand request. + /// + public class NodeFilter + { + /// + /// The name of the filter property + /// + public string DisplayName { get; set; } + + /// + /// The operator of the filter property + /// + public NodeFilterOperator Operator { get; set; } + + /// + /// The applied values of the filter property + /// + public JToken Value { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/ChildFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/ChildFactory.cs index 45e9cd9b..2286d8ae 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/ChildFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/ChildFactory.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; +using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel; namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes @@ -31,9 +32,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes /// /// Parent Node /// force to refresh - /// name of the sql object to filter - /// - public abstract IEnumerable Expand(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken); + /// name of the sql object to filter + /// include system objects + /// cancellation token + /// filters to apply + public abstract IEnumerable Expand(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken, IEnumerable? filters); /// /// The list of filters that should be applied on the smo object list diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/NodePropertyFilter.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/NodePropertyFilter.cs index 045d4e1a..1a5528f2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/NodePropertyFilter.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/NodePropertyFilter.cs @@ -46,6 +46,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes /// public bool IsNotFilter { get; set; } = false; + /// + /// Indicates if the values are for type datetime + /// + public bool IsDateTime { get; set; } = false; + /// /// Indicates the type of the filter. It can be EQUALS, DATETIME, FALSE or CONTAINS /// More information can be found here: @@ -82,36 +87,140 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes } StringBuilder filter = new StringBuilder(); + + foreach (var value in Values) { - object propertyValue = value; - if (Type == typeof(string)) - { - propertyValue = $"'{propertyValue}'"; - } - else if (Type == typeof(Enum)) - { - propertyValue = (int)Convert.ChangeType(value, Type); - } - string filterText = string.Empty; - switch (FilterType) + if (IsDateTime) { - case FilterType.EQUALS: - filterText = $"@{Property} = {propertyValue}"; - break; - case FilterType.DATETIME: - filterText = $"@{Property} = datetime({propertyValue})"; - break; - case FilterType.CONTAINS: - filterText = $"contains(@{Property}, {propertyValue})"; - break; - case FilterType.FALSE: - filterText = $"@{Property} = false()"; - break; - case FilterType.ISNULL: - filterText = $"isnull(@{Property})"; - break; + string Value1; + string Value2; + switch (FilterType) + { + case FilterType.BETWEEN: + string[] betweenValues = (string[])value; + Value1 = DateTime.Parse((string)betweenValues[0]).ToString("yyyy-MM-dd 00:00:00.000"); + Value2 = DateTime.Parse((string)betweenValues[1]).ToString("yyyy-MM-dd 23:59:59.999"); + filterText = $"@{Property} >= datetime('{Value1}') and @{Property} <= datetime('{Value2}')"; + break; + case FilterType.NOTBETWEEN: + IsNotFilter = true; + string[] notBetweenValues = (string[])value; + Value1 = DateTime.Parse((string)notBetweenValues[0]).ToString("yyyy-MM-dd 00:00:00.000"); + Value2 = DateTime.Parse((string)notBetweenValues[1]).ToString("yyyy-MM-dd 23:59:59.999"); + filterText = $"@{Property} >= datetime('{Value1}') and @{Property} <= datetime('{Value2}')"; + break; + case FilterType.EQUALS: + Value1 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 00:00:00.000"); + Value2 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 23:59:59.999"); + filterText = $"@{Property} >= datetime('{Value1}') and @{Property} <= datetime('{Value2}')"; + break; + case FilterType.GREATERTHAN: + Value1 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 23:59:59.999"); + filterText = $"@{Property} > datetime('{Value1}')"; + break; + case FilterType.LESSTHAN: + Value1 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 00:00:00.000"); + filterText = $"@{Property} < datetime('{Value1}')"; + break; + case FilterType.GREATERTHANOREQUAL: + Value1 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 00:00:00.000"); + filterText = $"@{Property} >= datetime('{Value1}')"; + break; + case FilterType.LESSTHANOREQUAL: + Value1 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 23:59:59.999"); + filterText = $"@{Property} <= datetime('{Value1}')"; + break; + case FilterType.NOTEQUALS: + IsNotFilter = true; + Value1 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 00:00:00.000"); + Value2 = DateTime.Parse((string)value).ToString("yyyy-MM-dd 23:59:59.999"); + filterText = $"@{Property} >= datetime('{Value1}') and @{Property} <= datetime('{Value2}')"; + break; + default: + break; + } + } + else if (IsNumericType(Type)) + { + switch (FilterType) + { + case FilterType.BETWEEN: + object[] betweenValues = (object[])value; + filterText = $"@{Property} >= {Decimal.Parse(betweenValues[0].ToString())} and @{Property} <= {Decimal.Parse(betweenValues[1].ToString())}"; + break; + case FilterType.NOTBETWEEN: + IsNotFilter = true; + object[] notBetweenValues = (object[])value; + filterText = $"@{Property} >= {Decimal.Parse(notBetweenValues[0].ToString())} and @{Property} <= {Decimal.Parse(notBetweenValues[1].ToString())}"; + break; + case FilterType.EQUALS: + filterText = $"@{Property} = {Decimal.Parse(value.ToString())}"; + break; + case FilterType.GREATERTHAN: + filterText = $"@{Property} > {Decimal.Parse(value.ToString())}"; + break; + case FilterType.LESSTHAN: + filterText = $"@{Property} < {Decimal.Parse(value.ToString())}"; + break; + case FilterType.GREATERTHANOREQUAL: + filterText = $"@{Property} >= {Decimal.Parse(value.ToString())}"; + break; + case FilterType.LESSTHANOREQUAL: + filterText = $"@{Property} <= {Decimal.Parse(value.ToString())}"; + break; + case FilterType.NOTEQUALS: + filterText = $"@{Property} != {Decimal.Parse(value.ToString())}"; + break; + default: + break; + } + } + else + { + object propertyValue = value; + if (Type == typeof(string)) + { + propertyValue = $"'{propertyValue}'"; + } + else if (Type == typeof(Enum)) + { + propertyValue = (int)Convert.ChangeType(value, Type); + } + switch (FilterType) + { + case FilterType.EQUALS: + filterText = $"@{Property} = {propertyValue}"; + break; + case FilterType.NOTEQUALS: + filterText = $"@{Property} != {propertyValue}"; + break; + case FilterType.LESSTHAN: + filterText = $"@{Property} < {propertyValue}"; + break; + case FilterType.GREATERTHAN: + filterText = $"@{Property} > {propertyValue}"; + break; + case FilterType.LESSTHANOREQUAL: + filterText = $"@{Property} <= {propertyValue}"; + break; + case FilterType.GREATERTHANOREQUAL: + filterText = $"@{Property} >= {propertyValue}"; + break; + case FilterType.DATETIME: + filterText = $"@{Property} = datetime({propertyValue})"; + break; + case FilterType.CONTAINS: + filterText = $"contains(@{Property}, {propertyValue})"; + break; + case FilterType.FALSE: + filterText = $"@{Property} = false()"; + break; + case FilterType.ISNULL: + filterText = $"isnull(@{Property})"; + break; + } } string orPrefix = filter.Length == 0 ? string.Empty : " or "; @@ -131,6 +240,27 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes } return string.Empty; } + + public static bool IsNumericType(Type type) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + default: + return false; + } + } } public enum FilterType @@ -139,6 +269,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes DATETIME, CONTAINS, FALSE, - ISNULL + ISNULL, + NOTEQUALS, + LESSTHAN, + GREATERTHAN, + LESSTHANOREQUAL, + GREATERTHANOREQUAL, + BETWEEN, + NOTBETWEEN } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs index 448a25b6..15e1e187 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs @@ -120,6 +120,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes /// public string NodeStatus { get; set; } + public NodeFilterProperty[] FilterProperties { get; set; } + /// /// Label to display to the user, describing this node. /// If not explicitly set this will fall back to the but @@ -235,7 +237,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes NodeStatus = this.NodeStatus, NodeSubType = this.NodeSubType, ErrorMessage = this.ErrorMessage, - ObjectType = this.NodeTypeId.ToString() + ObjectType = this.NodeTypeId.ToString(), + FilterableProperties = this.FilterProperties }; } @@ -243,14 +246,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes /// Expands this node and returns its children /// /// Children as an IList. This is the raw children collection, not a copy - public IList Expand(string name, CancellationToken cancellationToken, string? accessToken = null) + public IList Expand(string name, CancellationToken cancellationToken, string? accessToken = null, IEnumerable? filters = null) { // TODO consider why solution explorer has separate Children and Items options if (children.IsInitialized) { return children; } - PopulateChildren(false, name, cancellationToken, accessToken); + PopulateChildren(false, name, cancellationToken, accessToken, filters); return children; } @@ -258,19 +261,19 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes /// Expands this node and returns its children /// /// Children as an IList. This is the raw children collection, not a copy - public IList Expand(CancellationToken cancellationToken, string? accessToken = null) + public IList Expand(CancellationToken cancellationToken, string? accessToken = null, IEnumerable? filters = null) { - return Expand(null, cancellationToken, accessToken); + return Expand(null, cancellationToken, accessToken, filters); } /// /// Refresh this node and returns its children /// /// Children as an IList. This is the raw children collection, not a copy - public virtual IList Refresh(CancellationToken cancellationToken, string? accessToken = null) + public virtual IList Refresh(CancellationToken cancellationToken, string? accessToken = null, IEnumerable? filters = null) { // TODO consider why solution explorer has separate Children and Items options - PopulateChildren(true, null, cancellationToken, accessToken); + PopulateChildren(true, null, cancellationToken, accessToken, filters); return children; } @@ -322,7 +325,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes return Parent as T; } - protected virtual void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken, string? accessToken = null) + protected virtual void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken, string? accessToken = null, IEnumerable? filters = null) { Logger.Write(TraceEventType.Verbose, string.Format(CultureInfo.InvariantCulture, "Populating oe node :{0}", this.GetNodePath())); Debug.Assert(IsAlwaysLeaf == false); @@ -353,7 +356,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes try { Logger.Verbose($"Begin populate children for {this.GetNodePath()} using {factory.GetType()} factory"); - IEnumerable items = factory.Expand(this, refresh, name, includeSystemObjects, cancellationToken); + IEnumerable items = factory.Expand(this, refresh, name, includeSystemObjects, cancellationToken, filters); Logger.Verbose($"End populate children for {this.GetNodePath()} using {factory.GetType()} factory"); if (items != null) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index ca45a8fe..af77b8b1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -378,12 +378,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer } - internal Task ExpandNode(ObjectExplorerSession session, string nodePath, bool forceRefresh = false, SecurityToken? securityToken = null) + internal Task ExpandNode(ObjectExplorerSession session, string nodePath, bool forceRefresh = false, SecurityToken? securityToken = null, NodeFilter[]? filters = null) { - return Task.Run(() => QueueExpandNodeRequest(session, nodePath, forceRefresh, securityToken)); + return Task.Run(() => QueueExpandNodeRequest(session, nodePath, forceRefresh, securityToken, filters)); } - internal ExpandResponse QueueExpandNodeRequest(ObjectExplorerSession session, string nodePath, bool forceRefresh = false, SecurityToken? securityToken = null) + internal ExpandResponse QueueExpandNodeRequest(ObjectExplorerSession session, string nodePath, bool forceRefresh = false, SecurityToken? securityToken = null, NodeFilter[]? filters = null) { NodeInfo[] nodes = null; TreeNode? node = session.Root.FindNodeByPath(nodePath); @@ -448,12 +448,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer if (forceRefresh) { Logger.Verbose($"Forcing refresh for {nodePath}"); - nodes = node.Refresh(cancelToken, securityToken?.Token).Select(x => x.ToNodeInfo()).ToArray(); + nodes = node.Refresh(cancelToken, securityToken?.Token, filters).Select(x => x.ToNodeInfo()).ToArray(); } else { Logger.Verbose($"Expanding {nodePath}"); - nodes = node.Expand(cancelToken, securityToken?.Token).Select(x => x.ToNodeInfo()).ToArray(); + nodes = node.Expand(cancelToken, securityToken?.Token, filters).Select(x => x.ToNodeInfo()).ToArray(); } response.Nodes = nodes; response.ErrorMessage = node.ErrorMessage; @@ -649,7 +649,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, CancellationToken cancellationToken, bool forceRefresh = false) { ExpandResponse response = null; - response = await ExpandNode(session, expandParams.NodePath, forceRefresh, expandParams.SecurityToken); + response = await ExpandNode(session, expandParams.NodePath, forceRefresh, expandParams.SecurityToken, expandParams.Filters); if (cancellationToken.IsCancellationRequested) { Logger.Write(TraceEventType.Verbose, "OE expand canceled"); diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs index 6fe6a8e2..e1b7425a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs @@ -8,6 +8,8 @@ using System; using System.Threading; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; +using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; +using System.Collections.Generic; namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { @@ -53,7 +55,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer /// A Tree Node that matches the condition, or null if no matching node could be found public static TreeNode? FindNode(TreeNode node, Predicate condition, Predicate filter, bool expandIfNeeded = false) { - if(node == null) + if (node == null) { return null; } @@ -76,5 +78,95 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer } return null; } + + public static INodeFilter ConvertExpandNodeFilterToNodeFilter(NodeFilter filter, NodeFilterProperty filterProperty) + { + Type type = typeof(string); + + var IsDateTime = filterProperty.Type == NodeFilterPropertyDataType.Date; + + FilterType filterType = FilterType.EQUALS; + bool isNotFilter = false; + + object filterValue = null; + + switch (filterProperty.Type) + { + case NodeFilterPropertyDataType.String: + case NodeFilterPropertyDataType.Date: + case NodeFilterPropertyDataType.Choice: + type = typeof(string); + filterValue = filter.Value.ToString(); + break; + case NodeFilterPropertyDataType.Number: + type = typeof(int); + filterValue = filter.Value.ToObject(); + break; + case NodeFilterPropertyDataType.Boolean: + type = typeof(bool); + filterValue = filter.Value.ToObject() ? 1 : 0; + break; + } + + switch (filter.Operator) + { + case NodeFilterOperator.Equals: + filterType = FilterType.EQUALS; + break; + case NodeFilterOperator.NotEquals: + filterType = FilterType.EQUALS; + isNotFilter = true; + break; + case NodeFilterOperator.LessThan: + filterType = FilterType.LESSTHAN; + break; + case NodeFilterOperator.LessThanOrEquals: + filterType = FilterType.LESSTHANOREQUAL; + break; + case NodeFilterOperator.GreaterThan: + filterType = FilterType.GREATERTHAN; + break; + case NodeFilterOperator.GreaterThanOrEquals: + filterType = FilterType.GREATERTHANOREQUAL; + break; + case NodeFilterOperator.Contains: + filterType = FilterType.CONTAINS; + break; + case NodeFilterOperator.NotContains: + filterType = FilterType.CONTAINS; + isNotFilter = true; + break; + case NodeFilterOperator.Between: + filterType = FilterType.BETWEEN; + break; + case NodeFilterOperator.NotBetween: + filterType = FilterType.NOTBETWEEN; + isNotFilter = true; + break; + } + + + if (filter.Operator == NodeFilterOperator.Between || filter.Operator == NodeFilterOperator.NotBetween) + { + if (filterProperty.Type == NodeFilterPropertyDataType.Number) + { + filterValue = filter.Value.ToObject(); + } + else if (filterProperty.Type == NodeFilterPropertyDataType.Date) + { + filterValue = filter.Value.ToObject(); + } + } + + return new NodePropertyFilter + { + Property = filterProperty.Name, + Type = type, + Values = new List { filterValue }, + IsNotFilter = isNotFilter, + FilterType = filterType, + IsDateTime = IsDateTime + }; + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs index 64f6ddce..1f579ac3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs @@ -7,11 +7,13 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Threading; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel @@ -54,12 +56,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel } } - protected override void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken, string? accessToken = null) + protected override void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken, string? accessToken = null, IEnumerable? filters = null) { var smoQueryContext = this.GetContextAs(); if (IsAccessible(smoQueryContext)) { - base.PopulateChildren(refresh, name, cancellationToken, accessToken); + base.PopulateChildren(refresh, name, cancellationToken, accessToken, filters); } else { diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs index 73b075b8..47eb3380 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs @@ -12,6 +12,7 @@ using System.Globalization; using System.Linq; using System.Threading; using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; using Microsoft.SqlTools.Utility; @@ -20,12 +21,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel public class SmoChildFactoryBase : ChildFactory { private IEnumerable smoProperties; + public override IEnumerable ApplicableParents() { return null; } - public override IEnumerable Expand(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken) + public override IEnumerable Expand(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken, IEnumerable? filters = null) { List allChildren = new List(); @@ -33,7 +35,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel { if (this.PutFoldersAfterNodes) { - OnExpandPopulateNonFolders(allChildren, parent, refresh, name, cancellationToken); + OnExpandPopulateNonFolders(allChildren, parent, refresh, name, cancellationToken, filters); OnExpandPopulateFoldersAndFilter(allChildren, parent, includeSystemObjects); RemoveFoldersFromInvalidSqlServerVersions(allChildren, parent); } @@ -41,7 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel { OnExpandPopulateFoldersAndFilter(allChildren, parent, includeSystemObjects); RemoveFoldersFromInvalidSqlServerVersions(allChildren, parent); - OnExpandPopulateNonFolders(allChildren, parent, refresh, name, cancellationToken); + OnExpandPopulateNonFolders(allChildren, parent, refresh, name, cancellationToken, filters); } OnBeginAsyncOperations(parent); @@ -110,7 +112,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel /// /// List to which nodes should be added /// Parent the nodes are being added to - protected virtual void OnExpandPopulateNonFolders(IList allChildren, TreeNode parent, bool refresh, string name, CancellationToken cancellationToken) + protected virtual void OnExpandPopulateNonFolders(IList allChildren, TreeNode parent, bool refresh, string name, CancellationToken cancellationToken, IEnumerable? appliedFilters = null) { Logger.Write(TraceEventType.Verbose, string.Format(CultureInfo.InvariantCulture, "child factory parent :{0}", parent.GetNodePath())); @@ -131,6 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IEnumerable queriers = context.ServiceProvider.GetServices(IsCompatibleQuerier); var filters = this.Filters.ToList(); var smoProperties = this.SmoProperties.Where(p => ServerVersionHelper.IsValidFor(serverValidFor, p.ValidFor)).Select(x => x.Name); + var filterDefinitions = parent.FilterProperties; if (!string.IsNullOrEmpty(name)) { filters.Add(new NodePropertyFilter @@ -140,6 +143,15 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel Values = new List { name }, }); } + if (appliedFilters != null) + { + foreach (var f in appliedFilters) + { + NodeFilterProperty filterProperty = filterDefinitions.FirstOrDefault(x => x.DisplayName == f.DisplayName); + filters.Add(ObjectExplorerUtils.ConvertExpandNodeFilterToNodeFilter(f, filterProperty)); + } + } + foreach (var querier in queriers) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.cs index c7017279..4c2489d1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.cs @@ -14,9 +14,11 @@ using System.Collections.Generic; using System.Composition; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; +using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Workspace; + namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel { @@ -176,6 +178,30 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Databases, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Security, @@ -761,6 +787,55 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Tables, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "DurabilityType", + DisplayName = SR.FilterDurabilityType, + Type = NodeFilterPropertyDataType.Choice, + Description = SR.FilterDurabilityTypeDescription, + Choices = new string[] { + "SchemaAndData", + "SchemaOnly", + } + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } if (!WorkspaceService.Instance.CurrentSettings.SqlTools.ObjectExplorer.GroupBySchema) @@ -770,6 +845,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Views, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } if (!WorkspaceService.Instance.CurrentSettings.SqlTools.ObjectExplorer.GroupBySchema) @@ -951,12 +1057,92 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Tables, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "DurabilityType", + DisplayName = SR.FilterDurabilityType, + Type = NodeFilterPropertyDataType.Choice, + Description = SR.FilterDurabilityTypeDescription, + Choices = new string[] { + "SchemaAndData", + "SchemaOnly", + } + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Views, NodeTypeId = NodeTypes.Views, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Synonyms, @@ -1094,6 +1280,55 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = true, IsMsShippedOwned = true, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "DurabilityType", + DisplayName = SR.FilterDurabilityType, + Type = NodeFilterPropertyDataType.Choice, + Description = SR.FilterDurabilityTypeDescription, + Choices = new string[] { + "SchemaAndData", + "SchemaOnly", + } + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_DroppedLedgerTables, @@ -1101,6 +1336,55 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.Sql2022OrHigher|ValidForFlag.AzureV12, SortPriority = Int32.MaxValue, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "DurabilityType", + DisplayName = SR.FilterDurabilityType, + Type = NodeFilterPropertyDataType.Choice, + Description = SR.FilterDurabilityTypeDescription, + Choices = new string[] { + "SchemaAndData", + "SchemaOnly", + } + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } @@ -1178,6 +1462,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsMsShippedOwned = true, ValidFor = ValidForFlag.Sql2022OrHigher|ValidForFlag.AzureV12, SortPriority = Int32.MaxValue, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } @@ -1236,6 +1551,44 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.StoredProcedures, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "IsNativelyCompiled", + DisplayName = SR.FilterIsNativelyCompiled, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsNativelyCompiledDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } if (!WorkspaceService.Instance.CurrentSettings.SqlTools.ObjectExplorer.GroupBySchema) @@ -1280,6 +1633,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.Sql2012OrHigher|ValidForFlag.AzureV12, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } } @@ -1306,6 +1690,44 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.StoredProcedures, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "IsNativelyCompiled", + DisplayName = SR.FilterIsNativelyCompiled, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsNativelyCompiledDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Functions, @@ -1327,6 +1749,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.Sql2012OrHigher|ValidForFlag.AzureV12, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } @@ -1747,6 +2200,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Columns, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "InPrimaryKey", + DisplayName = SR.FilterInPrimaryKey, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterInPrimaryKeyDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Keys, @@ -1774,6 +2244,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.NotSqlDemand, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Statistics, @@ -1813,6 +2300,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Columns, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "InPrimaryKey", + DisplayName = SR.FilterInPrimaryKey, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterInPrimaryKeyDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Constraints, @@ -1826,6 +2330,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.NotSqlDemand, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Statistics, @@ -1867,6 +2388,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Columns, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "InPrimaryKey", + DisplayName = SR.FilterInPrimaryKey, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterInPrimaryKeyDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Statistics, @@ -2238,6 +2776,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel NodeTypeId = NodeTypes.Columns, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "InPrimaryKey", + DisplayName = SR.FilterInPrimaryKey, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterInPrimaryKeyDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Triggers, @@ -2252,6 +2807,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.NotSqlDemand, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "IsMemoryOptimized", + DisplayName = SR.FilterIsMemoryOptimized, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsMemoryOptimizedDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_Statistics, @@ -2301,12 +2873,88 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.NotSqlDw, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "IsNativelyCompiled", + DisplayName = SR.FilterIsNativelyCompiled, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsNativelyCompiledDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_ScalarValuedFunctions, NodeTypeId = NodeTypes.ScalarValuedFunctions, IsSystemObject = false, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "IsNativelyCompiled", + DisplayName = SR.FilterIsNativelyCompiled, + Type = NodeFilterPropertyDataType.Boolean, + Description = SR.FilterIsNativelyCompiledDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); currentChildren.Add(new FolderNode { NodeValue = SR.SchemaHierarchy_AggregateFunctions, @@ -2314,6 +2962,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel IsSystemObject = false, ValidFor = ValidForFlag.AllOnPrem|ValidForFlag.AzureV12, SortPriority = SmoTreeNode.NextSortPriority, + FilterProperties = new NodeFilterProperty[] + { + new NodeFilterProperty + { + Name = "Name", + DisplayName = SR.FilterName, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterNameDescription, + }, + new NodeFilterProperty + { + Name = "Schema", + DisplayName = SR.FilterSchema, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterSchemaDescription, + }, + new NodeFilterProperty + { + Name = "Owner", + DisplayName = SR.FilterOwner, + Type = NodeFilterPropertyDataType.String, + Description = SR.FilterOwnerDescription, + }, + new NodeFilterProperty + { + Name = "CreateDate", + DisplayName = SR.FilterCreateDate, + Type = NodeFilterPropertyDataType.Date, + Description = SR.FilterCreateDateDescription, + }, + } }); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.tt b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.tt index b4f29f82..0aaac561 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.tt +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodes.tt @@ -25,9 +25,11 @@ using System.Collections.Generic; using System.Composition; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; +using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Workspace; + namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel { @@ -128,7 +130,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel List children = GetChildren(xmlFile, type); List smoProperties = GetNodeSmoProperties(xmlFile, type); - // Load and parse Filters node // A node is comprised of and nodes // - A node defines the properties to construct a NodePropertyFilter object @@ -299,6 +300,57 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel WriteLine(" ValidFor = {0},", GetValidForFlags(validFor)); } WriteLine(" SortPriority = {0},", sortPriority); + List filterProperties = GetFilterProperties(xmlFile, childName); + if(filterProperties.Count > 0) + { + WriteLine(" FilterProperties = new NodeFilterProperty[]"); + WriteLine(" {"); + foreach (var filterDef in filterProperties) + { + var filterName = filterDef.GetAttribute("Name"); + var filterDisplayName = filterDef.GetAttribute("LocLabel"); + var filterType = filterDef.GetAttribute("Type"); + var enumString = "NodeFilterPropertyDataType"; + switch (filterType) + { + case "string": + enumString += ".String"; + break; + case "bool": + enumString += ".Boolean"; + break; + case "date": + enumString += ".Date"; + break; + case "choice": + enumString += ".Choice"; + break; + } + var filterDescription = filterDef.GetAttribute("Description"); + WriteLine(" new NodeFilterProperty"); + WriteLine(" {"); + WriteLine(" Name = \"{0}\",", filterName); + WriteLine(" DisplayName = {0},", filterDisplayName); + WriteLine(" Type = {0},", enumString); + WriteLine(" Description = {0},", filterDescription); + if(filterType == "choice") + { + var enumValues = filterDef.ChildNodes; + WriteLine(" Choices = new string[] {"); + foreach (XmlElement enumValue in enumValues) + { + var enumValueName = enumValue.GetAttribute("Name"); + WriteLine(" \"{0}\",", enumValueName); + + } + WriteLine(" }"); + } + WriteLine(" },"); + + } + WriteLine(" }"); + } + WriteLine(" });"); } @@ -580,6 +632,33 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel return retElements; } + + public static List GetFilterProperties(string xmlFile, string nodeType) + { + XmlElement nodeElement = GetNodeElement(xmlFile, nodeType); + XmlDocument doc = new XmlDocument(); + doc.Load(xmlFile); + + List retElements = new List(); + XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/FilterProperties[@NodeName='{0}']/Property", nodeType)); + foreach (XmlNode item in nodeList) + { + var filterName = item.InnerText; + XmlNodeList filterPropertyList = doc.SelectNodes(string.Format("/ServerExplorerTree/FilterProperty[@Name='{0}']", filterName)); + foreach (var filterProperty in filterPropertyList) + { + XmlElement itemAsElement = filterProperty as XmlElement; + if (itemAsElement != null) + { + retElements.Add(itemAsElement); + } + } + } + return retElements; + } + + + public static List GetNodeFilterValues(string xmlFile, string parentName, string filterProperty, bool orFilter = false) { XmlElement nodeElement = GetNodeElement(xmlFile, parentName); diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodesDefinition.xml b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodesDefinition.xml index 529cfa02..c7117777 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodesDefinition.xml +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNodesDefinition.xml @@ -565,4 +565,113 @@ + + + Name + Owner + CreateDate + + + + Name + Schema + Owner + DurabilityType + IsMemoryOptimized + CreateDate + + + + Name + Schema + Owner + DurabilityType + IsMemoryOptimized + CreateDate + + + + Name + Schema + Owner + DurabilityType + IsMemoryOptimized + CreateDate + + + + Name + Schema + Owner + CreateDate + + + + Name + Schema + Owner + CreateDate + + + + Name + Schema + Owner + IsNativelyCompiled + CreateDate + + + + Name + Schema + Owner + IsNativelyCompiled + CreateDate + + + + Name + Schema + Owner + IsNativelyCompiled + CreateDate + + + + Name + Schema + Owner + CreateDate + + + + Name + Schema + Owner + CreateDate + + + + Name + IsMemoryOptimized + + + + Name + InPrimaryKey + + + + + + + + + + + + + + + diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/NodeFilterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/NodeFilterTests.cs index b9a7fece..c81110a4 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/NodeFilterTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/NodeFilterTests.cs @@ -19,39 +19,39 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer { // Basic PropertyFilter definitions to use in tests - public NodePropertyFilter TemporalFilter = + public NodePropertyFilter TemporalFilter = new NodePropertyFilter - { - Property = "TemporalType", - Type = typeof(Enum), - TypeToReverse = typeof(SqlHistoryTableQuerier), - ValidFor = ValidForFlag.Sql2016|ValidForFlag.Sql2017|ValidForFlag.Sql2019|ValidForFlag.Sql2022OrHigher|ValidForFlag.AzureV12, - Values = new List + { + Property = "TemporalType", + Type = typeof(Enum), + TypeToReverse = typeof(SqlHistoryTableQuerier), + ValidFor = ValidForFlag.Sql2016 | ValidForFlag.Sql2017 | ValidForFlag.Sql2019 | ValidForFlag.Sql2022OrHigher | ValidForFlag.AzureV12, + Values = new List { { TableTemporalType.HistoryTable } } - }; + }; - public NodePropertyFilter LedgerHistoryFilter = + public NodePropertyFilter LedgerHistoryFilter = new NodePropertyFilter - { - Property = "LedgerType", - Type = typeof(Enum), - TypeToReverse = typeof(SqlHistoryTableQuerier), - ValidFor = ValidForFlag.Sql2022OrHigher|ValidForFlag.AzureV12, - Values = new List + { + Property = "LedgerType", + Type = typeof(Enum), + TypeToReverse = typeof(SqlHistoryTableQuerier), + ValidFor = ValidForFlag.Sql2022OrHigher | ValidForFlag.AzureV12, + Values = new List { { LedgerTableType.HistoryTable } } - }; + }; public NodePropertyFilter SystemObjectFilter = new NodePropertyFilter - { - Property = "IsSystemObject", - Type = typeof(bool), - Values = new List { 1 }, - }; + { + Property = "IsSystemObject", + Type = typeof(bool), + Values = new List { 1 }, + }; /// /// Validates the output of the ToPropertyFilterString for the NodeOrFilter class @@ -59,7 +59,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer [Test] public void NodeOrFilterReturnsProperString() { - var orNode = new NodeOrFilter { + var orNode = new NodeOrFilter + { FilterList = new List { TemporalFilter, LedgerHistoryFilter @@ -127,7 +128,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer [Test] public void GetPropertyFilterWithNodePropertyAndNodeOrFilters() { - var orNode = new NodeOrFilter { + var orNode = new NodeOrFilter + { FilterList = new List { TemporalFilter, LedgerHistoryFilter @@ -145,7 +147,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer string sql2016ServerVersion = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2016); string expectedSql2016Filters = "[((@TemporalType = 1)) and (@IsSystemObject = 1)]"; - Assert.That(sql2016ServerVersion, Is.EqualTo(expectedSql2016Filters), "GetPropertyFilter did not construct the URN filter string as expected when excluding filters that aren't valid for the given server type."); + Assert.That(sql2016ServerVersion, Is.EqualTo(expectedSql2016Filters), "GetPropertyFilter did not construct the URN filter string as expected when excluding filters that aren't valid for the given server type."); string invalidQuerierType = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlTableQuerier), ValidForFlag.Sql2022OrHigher); string expectedTableQuerierFilters = "[(@IsSystemObject = 1)]"; @@ -160,14 +162,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer public void GetPropertyFilterWithMixedFilters() { // All these filters together are nonsense, but it's just testing the logic for constructing the filter string - var orNode = new NodeOrFilter { + var orNode = new NodeOrFilter + { FilterList = new List { TemporalFilter, LedgerHistoryFilter } }; - var orNode2 = new NodeOrFilter { + var orNode2 = new NodeOrFilter + { FilterList = new List { SystemObjectFilter, LedgerHistoryFilter, @@ -200,7 +204,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer TemporalFilter, LedgerHistoryFilter }; - + var nodeList = new List { new NodePropertyFilter(){ Property = "Schema", @@ -218,5 +222,393 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer string expectedNewUrn = "[(@TemporalType = 1) and (@LedgerType = 1) and (@Schema = 'jsdafl983!@$#%535343]]]][[[')]"; Assert.That(newUrn, Is.EqualTo(expectedNewUrn), "GetPropertyFilter did not construct the URN filter string as expected"); } + + [Test] + public void TestDateFilters() + { + // Testing date filter with equals + var filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "2021-01-01" }, + FilterType = FilterType.EQUALS, + IsDateTime = true + } + }; + + string filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@CreateDate >= datetime('2021-01-01 00:00:00.000') and @CreateDate <= datetime('2021-01-01 23:59:59.999'))]", filterString, "Error parsing date filter with equals operator"); + + + // Testing date filter with less than + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "2021-01-01" }, + FilterType = FilterType.LESSTHAN, + IsDateTime = true + } + }; + + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@CreateDate < datetime('2021-01-01 00:00:00.000'))]", filterString, "Error parsing date filter with less than operator"); + + // Testing date filter with greater than + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "2021-01-01" }, + FilterType = FilterType.GREATERTHAN, + IsDateTime = true + } + }; + + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@CreateDate > datetime('2021-01-01 23:59:59.999'))]", filterString, "Error parsing date filter with greater than operator"); + + // Testing date filter with between + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { new string[] {"2021-01-01", "2021-01-02"}}, + FilterType = FilterType.BETWEEN, + IsDateTime = true + } + }; + + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@CreateDate >= datetime('2021-01-01 00:00:00.000') and @CreateDate <= datetime('2021-01-02 23:59:59.999'))]", filterString, "Error parsing date filter with between operator"); + + + // Testing date filter with not equals + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "2021-01-01" }, + FilterType = FilterType.NOTEQUALS, + IsDateTime = true, + } + }; + + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(not(@CreateDate >= datetime('2021-01-01 00:00:00.000') and @CreateDate <= datetime('2021-01-01 23:59:59.999')))]", filterString, "Error parsing date filter with not equals operator"); + + // Testing date filter with not between + + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { new string[] {"2021-01-01", "2021-01-02"}}, + FilterType = FilterType.NOTBETWEEN, + IsDateTime = true + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(not(@CreateDate >= datetime('2021-01-01 00:00:00.000') and @CreateDate <= datetime('2021-01-02 23:59:59.999')))]", filterString, "Error parsing date filter with not between operator"); + + // Testing date filter LessThanOrEquals + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "2021-01-01" }, + FilterType = FilterType.LESSTHANOREQUAL, + IsDateTime = true + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@CreateDate <= datetime('2021-01-01 23:59:59.999'))]", filterString, "Error parsing date filter with LessThanOrEquals operator"); + + // Testing date filter GreaterThanOrEquals + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "2021-01-01" }, + FilterType = FilterType.GREATERTHANOREQUAL, + IsDateTime = true + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@CreateDate >= datetime('2021-01-01 00:00:00.000'))]", filterString, "Error parsing date filter with GreaterThanOrEquals operator"); + + + // Testing date filter with invalid date + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { "invalid value" }, + FilterType = FilterType.EQUALS, + IsDateTime = true + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown for creating a date sfc filter with invalid date"); + + // Testing date filter with invalid date for between operator + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { new string[] {"invalid value", "2021-01-02"}}, + FilterType = FilterType.BETWEEN, + IsDateTime = true + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when value array contains invalid date value for between operator"); + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List {"2021-01-02"}, + FilterType = FilterType.BETWEEN, + IsDateTime = true + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when only one date value is provided for between operator"); + filterList = new List + { + new NodePropertyFilter() + { + Property = "CreateDate", + Type = typeof(string), + ValidFor = ValidForFlag.All, + Values = new List { new string[] {"2021-01-02"}}, + FilterType = FilterType.BETWEEN, + IsDateTime = true + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when only one value is provided in date array for between operator"); + } + + [Test] + public void TextNumericFilters() + { + // Testing numeric filter with equals + var filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { "100" }, + FilterType = FilterType.EQUALS, + IsDateTime = false + } + }; + + string filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount = 100)]", filterString, "Error parsing numeric filter with equals operator"); + + // Testing numeric filter with less than + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { 100 }, + FilterType = FilterType.LESSTHAN, + IsDateTime = false + } + }; + + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount < 100)]", filterString, "Error parsing numeric filter with less than operator"); + + // Testing numeric filter with greater than + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { 100 }, + FilterType = FilterType.GREATERTHAN, + IsDateTime = false + } + }; + + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount > 100)]", filterString, "Error parsing numeric filter with greater than operator"); + + // Testing numeric filter with between + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { new object[] {100, 200}}, + FilterType = FilterType.BETWEEN, + IsDateTime = false + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount >= 100 and @RowCount <= 200)]", filterString, "Error parsing numeric filter with between operator"); + + // Testing numeric filter with not equals + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { 100 }, + FilterType = FilterType.NOTEQUALS, + IsDateTime = false, + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount != 100)]", filterString, "Error parsing numeric filter with not equals operator"); + + // Testing numeric filter with not between + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { new object[] {100, 200}}, + FilterType = FilterType.NOTBETWEEN, + IsDateTime = false + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(not(@RowCount >= 100 and @RowCount <= 200))]", filterString, "Error parsing numeric filter with not between operator"); + + // Testing numeric filter LessThanOrEquals + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { 100 }, + FilterType = FilterType.LESSTHANOREQUAL, + IsDateTime = false + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount <= 100)]", filterString, "Error parsing numeric filter with LessThanOrEquals operator"); + + // Testing numeric filter GreaterThanOrEquals + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { 100 }, + FilterType = FilterType.GREATERTHANOREQUAL, + IsDateTime = false + } + }; + filterString = INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All); + Assert.AreEqual("[(@RowCount >= 100)]", filterString, "Error parsing numeric filter with GreaterThanOrEquals operator"); + + // Testing numeric filter with invalid value + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { "invalid value" }, + FilterType = FilterType.EQUALS, + IsDateTime = false + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown for creating a numeric sfc filter with invalid number"); + + // Testing numeric filter with invalid value for between operator + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { new object[] {"invalid value", 200}}, + FilterType = FilterType.BETWEEN, + IsDateTime = false + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown for creating a numberic sfc filter with invalid array for between operator"); + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List {200}, + FilterType = FilterType.BETWEEN, + IsDateTime = false + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when a single value is passed for between operator"); + filterList = new List + { + new NodePropertyFilter() + { + Property = "RowCount", + Type = typeof(int), + ValidFor = ValidForFlag.All, + Values = new List { new object[] {200}}, + FilterType = FilterType.BETWEEN, + IsDateTime = false + } + }; + Assert.Throws(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when the array contains single value for between operator"); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs index 2ebd0198..a3b0a92a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs @@ -6,6 +6,7 @@ #nullable disable using System; +using System.Collections.Generic; using Microsoft.Data.SqlClient; using System.Linq; using System.Threading; @@ -297,7 +298,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer public void FindNodeCanExpandParentNodes() { var mockTreeNode = new Mock(); - object[] populateChildrenArguments = { ItExpr.Is(x => x == false), ItExpr.IsNull(), new CancellationToken(), ItExpr.IsNull() }; + object[] populateChildrenArguments = { ItExpr.Is(x => x == false), ItExpr.IsNull(), new CancellationToken(), ItExpr.IsNull(), ItExpr.IsNull>() }; mockTreeNode.Protected().Setup("PopulateChildren", populateChildrenArguments); mockTreeNode.Object.IsAlwaysLeaf = false;