Or Filtering on Object Explorer Nodes (#1608)

support for a more robust filtering system in the Object Explorer xml, allowing for or-ing filter properties together for use in URN querying for objects

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
This commit is contained in:
Jordan Hays
2022-07-29 18:35:37 -04:00
committed by GitHub
parent 40df024dbc
commit 3294a52ad9
10 changed files with 1229 additions and 827 deletions

View File

@@ -15,6 +15,18 @@ SmoTreeNodesDefinition.xml defines all the hierarchies and all the supported obj
* How to filter objects based on the properties in each node
* How to query each object (Reference to another generated code to query each object type)
#### Filters
\<Filters> is an optional part of an SMO Node definition, which is used to filter objects based on the expected properties for a certain node type.
A \<Filters> node is comprised of \<Or> and \<Filter> nodes
- A \<Filter> node defines the properties to construct a NodePropertyFilter object
- An \<Or> node defines a list of \<Filter> nodes that are or'ed in the resulting URN Query
\<Filters> can have an arbitrary number of top-level \<Or> and \<Filter> nodes.
All filters defined at the top-level are and'ed in the resulting URN Query.
This is generated into the `IEnumerable<INodeFilter> Filters` property for a particular SMO object in SmoTreeNodes.cs.
### SmoQueryModelDefinition.xml
SmoQueryModelDefinition.xml defines the supported object types and how to query each type using SMO library. ChildQuerierTypes attribute in SmoTreeNodesDefinition.xml nodes has reference to the types in this xml file. It includes:
@@ -27,7 +39,7 @@ To get each object type, SMO by default only gets the name and schema and not al
To optimize the query to get the properties needed to create each node, add the properties to the node element in SmoTreeNodesDefinition.xml.
For example, to get the table node, we also need to get two properties IsSystemVersioned and TemporalType which are included as properties in the table node:
For example, to get the table node, we also need to get three properties IsSystemVersioned, LedgerType, and TemporalType which are included as properties in the table node:
### Sample
@@ -39,15 +51,38 @@ For example, to get the table node, we also need to get two properties IsSystemV
<Value>TableTemporalType.None</Value>
<Value>TableTemporalType.SystemVersioned</Value>
</Filter>
<Filter Property="LedgerType" Type="Enum" ValidFor="Sql2022|AzureV12">
<Value>LedgerTableType.None</Value>
<Value>LedgerTableType.UpdatableLedgerTable</Value>
<Value>LedgerTableType.AppendOnlyLedgerTable</Value>
</Filter>
</Filters>
<Properties>
<Property Name="IsSystemVersioned" ValidFor="Sql2016|Sql2017|AzureV12"/>
<Property Name="TemporalType" ValidFor="Sql2016|Sql2017|AzureV12"/>
<Property Name="TemporalType" ValidFor="Sql2016|Sql2017|AzureV12"/>
<Property Name="LedgerType" ValidFor="Sql2022|AzureV12"/>
</Properties>
<Child Name="SystemTables" IsSystemObject="1"/>
</Node>
```
### Sample \<Or> Node Usage
```xml
<Node Name="Table" LocLabel="string.Empty" BaseClass="ModelBased" IsAsyncLoad="" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlTable;SqlHistoryTable" TreeNode="HistoryTableTreeNode">
<Filters>
<Or>
<Filter TypeToReverse="SqlHistoryTable" Property="TemporalType" Type="Enum" ValidFor="Sql2016|Sql2017|Sql2019|Sql2022|AzureV12">
<Value>TableTemporalType.HistoryTable</Value>
</Filter>
<Filter TypeToReverse="SqlHistoryTable" Property="LedgerType" Type="Enum" ValidFor="Sql2022|AzureV12">
<Value>LedgerTableType.HistoryTable</Value>
</Filter>
</Or>
</Filters>
</Node>
```
## Guides
### Add a new SQL object type

View File

@@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
/// <summary>
/// The list of filters that should be applied on the smo object list
/// </summary>
public abstract IEnumerable<NodeFilter> Filters { get; }
public abstract IEnumerable<INodeFilter> Filters { get; }
/// <summary>
/// The list of properties to be loaded with the object

View File

@@ -0,0 +1,57 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// Has information for filtering a SMO object by properties
/// </summary>
public interface INodeFilter
{
/// <summary>
/// Returns true if the filter can be apply to the given type and Server type
/// </summary>
/// <param name="type">Type of the querier</param>
/// <param name="validForFlag">Server Type</param>
/// <returns></returns>
bool CanApplyFilter(Type type, ValidForFlag validForFlag);
/// <summary>
/// Creates a string from the filter property and values to be used in the Urn to query the SQL objects
/// Example of the output:[@ IsSystemObject = 0]
/// </summary>
/// <returns></returns>
string ToPropertyFilterString(Type type, ValidForFlag validForFlag);
/// <summary>
/// Creates a fully paramaterized property filter string for the URN query for SQL objects.
/// Example of the output:[@ IsSystemObject = 0]
/// </summary>
/// <returns></returns>
public static string GetPropertyFilter(IEnumerable<INodeFilter> filters, Type type, ValidForFlag validForFlag)
{
StringBuilder filter = new StringBuilder();
foreach (var value in filters)
{
string andPrefix = filter.Length == 0 ? string.Empty : " and ";
var filterString = value.ToPropertyFilterString(type, validForFlag);
if (filterString != string.Empty) {
filter.Append($"{andPrefix}{filterString}");
}
}
if (filter.Length != 0)
{
return "[" + filter.ToString() + "]";
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,58 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// Has information for filtering a SMO object by properties
/// </summary>
public class NodeOrFilter : INodeFilter
{
/// <summary>
/// Filter values
/// </summary>
public List<NodePropertyFilter> FilterList { get; set; }
/// <summary>
/// Returns true if any of the filters within the FilterList apply to the type and server type.
/// </summary>
/// <param name="type">Type of the querier</param>
/// <param name="validForFlag">Server Type</param>
public bool CanApplyFilter(Type type, ValidForFlag validForFlag) {
return this.FilterList.Exists(f => f.CanApplyFilter(type, validForFlag));
}
/// <summary>
/// Creates a string representation of a node "or" filter, which is combined in the INodeFilter interface to construct the filter used in the URN to query the SQL objects.
/// Example of the output: ((@TableTemporalType = 1) or (@LedgerTableType = 1))
/// </summary>
/// <returns></returns>
public string ToPropertyFilterString(Type type, ValidForFlag validForFlag)
{
StringBuilder filter = new StringBuilder();
foreach (var nodeFilter in FilterList)
{
string orPrefix = filter.Length == 0 ? string.Empty : " or ";
// For "or" filter, have to check each node as it's processed for whether it's valid.
var filterString = nodeFilter.ToPropertyFilterString(type, validForFlag);
if (filterString != string.Empty)
{
filter.Append($"{orPrefix}{filterString}");
}
}
if (filter.Length != 0)
{
return "(" + filter.ToString() + ")";
}
return string.Empty;
}
}
}

View File

@@ -5,14 +5,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// Has information for filtering a SMO object by properties
/// </summary>
public class NodeFilter
public class NodePropertyFilter : INodeFilter
{
/// <summary>
/// Property name
@@ -55,52 +55,40 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
}
/// <summary>
/// Creates a string from the filter property and values to be used in the Urn to query the SQL objects
/// Example of the output:[@ IsSystemObject = 0]
/// Creates a string representation of a given node filter, which is combined in the INodeFilter interface to construct the filter used in the URN to query the SQL objects.
/// Example of the output: (@ IsSystemObject = 0)
/// </summary>
/// <returns></returns>
public string ToPropertyFilterString()
public string ToPropertyFilterString(Type type, ValidForFlag validForFlag)
{
string filter = "";
List<object> values = Values;
if (values != null)
// check first if the filter can be applied; if not just return empty string
if (!CanApplyFilter(type, validForFlag))
{
for (int i = 0; i < values.Count; i++)
return string.Empty;
}
StringBuilder filter = new StringBuilder();
foreach (var value in Values)
{
object propertyValue = value;
if (Type == typeof(string))
{
var value = values[i];
object propertyValue = value;
if (Type == typeof(string))
{
propertyValue = $"'{propertyValue}'";
}
if (Type == typeof(Enum))
{
propertyValue = (int)Convert.ChangeType(value, Type);
}
string orPrefix = i == 0 ? string.Empty : "or";
filter = $"{filter} {orPrefix} @{Property} = {propertyValue}";
propertyValue = $"'{propertyValue}'";
}
else if (Type == typeof(Enum))
{
propertyValue = (int)Convert.ChangeType(value, Type);
}
string orPrefix = filter.Length == 0 ? string.Empty : " or ";
filter.Append($"{orPrefix}@{Property} = {propertyValue}");
}
filter = $"({filter})";
return filter;
}
public static string ConcatProperties(IEnumerable<NodeFilter> filters)
{
string filter = "";
var list = filters.ToList();
for (int i = 0; i < list.Count; i++)
if (filter.Length != 0)
{
var value = list[i];
string andPrefix = i == 0 ? string.Empty : "and";
filter = $"{filter} {andPrefix} {value.ToPropertyFilterString()}";
return "(" + filter.ToString() + ")";
}
filter = $"[{filter}]";
return filter;
return string.Empty;
}
}
}

View File

@@ -104,7 +104,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
var smoProperties = this.SmoProperties.Where(p => ServerVersionHelper.IsValidFor(serverValidFor, p.ValidFor)).Select(x => x.Name);
if (!string.IsNullOrEmpty(name))
{
filters.Add(new NodeFilter
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
@@ -118,7 +118,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
continue;
}
string propertyFilter = GetProperyFilter(filters, querier.GetType(), serverValidFor);
string propertyFilter = INodeFilter.GetPropertyFilter(filters, querier.GetType(), serverValidFor);
try
{
var smoObjectList = querier.Query(context, propertyFilter, refresh, smoProperties).ToList();
@@ -162,22 +162,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
return filterTheNode;
}
private string GetProperyFilter(IEnumerable<NodeFilter> filters, Type querierType, ValidForFlag validForFlag)
{
string filter = string.Empty;
if (filters != null)
{
var filtersToApply = filters.Where(f => f.CanApplyFilter(querierType, validForFlag)).ToList();
filter = string.Empty;
if (filtersToApply.Any())
{
filter = NodeFilter.ConcatProperties(filtersToApply);
}
}
return filter;
}
private bool IsCompatibleQuerier(SmoQuerier querier)
{
if (ChildQuerierTypes == null)
@@ -257,11 +241,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
}
}
public override IEnumerable<NodeFilter> Filters
public override IEnumerable<INodeFilter> Filters
{
get
{
return Enumerable.Empty<NodeFilter>();
return Enumerable.Empty<INodeFilter>();
}
}

View File

@@ -73,13 +73,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
var name = TreeNode.GetAttribute("Name");
WriteLine(string.Format(" internal sealed partial class {0} : SmoTreeNode", name));
WriteLine(" {");
WriteLine(string.Format(" public {0}() : base()", name));
WriteLine(" {");
WriteLine(" NodeValue = string.Empty;");
WriteLine(string.Format(" this.NodeType = \"{0}\";", name.Replace("TreeNode", string.Empty)));
WriteLine(string.Format(" this.NodeTypeId = NodeTypes.{0};", name.Replace("TreeNode", string.Empty)));
WriteLine(" OnInitialize();");
WriteLine(" }");
WriteLine(string.Format(" public {0}() : base()", name));
WriteLine(" {");
WriteLine(" NodeValue = string.Empty;");
WriteLine(string.Format(" this.NodeType = \"{0}\";", name.Replace("TreeNode", string.Empty)));
WriteLine(string.Format(" this.NodeTypeId = NodeTypes.{0};", name.Replace("TreeNode", string.Empty)));
WriteLine(" OnInitialize();");
WriteLine(" }");
WriteLine(" }");
WriteLine("");
}
@@ -116,86 +116,101 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
WriteLine(string.Format(" public override IEnumerable<string> ApplicableParents() {{ return new[] {{ \"{0}\" }}; }}", type));
List<XmlElement> children = GetChildren(xmlFile, type);
List<XmlElement> filters = GetNodeFilters(xmlFile, type);
List<XmlElement> smoProperties = GetNodeSmoProperties(xmlFile, type);
if (filters.Count > 0)
// Load and parse Filters node
// A <Filters> node is comprised of <Or> and <Filter> nodes
// - A <Filter> node defines the properties to construct a NodePropertyFilter object
// - An <Or> node defines a list of <Filter> nodes that are or'ed in the resulting URN Query
// <Filters> can have an arbitrary number of top-level <Or> and <Filter> nodes.
// All filters defined at the top-level are and'ed in the resulting URN Query.
// This is generated into the IEnumerable<INodeFilter> Filters object for a particular SMO object.
// Currently there is not support for nested <Or> nodes
XmlNode filtersNode = GetFiltersNode(xmlFile, type);
if (filtersNode != null)
{
// Get the children nodes for the Filters
XmlNodeList childNodes = filtersNode.ChildNodes;
if (childNodes.Count > 0)
{
// Write initial declarator for the filters object
WriteLine("");
WriteLine(" public override IEnumerable<NodeFilter> Filters");
WriteLine(" public override IEnumerable<INodeFilter> Filters");
WriteLine(" {");
WriteLine(" get");
WriteLine(" {");
WriteLine(" get");
WriteLine(" {");
WriteLine(" var filters = new List<INodeFilter>();");
WriteLine(" var filters = new List<NodeFilter>();");
foreach (var filter in filters)
// Parse each of the <Or> nodes in <Filters>
foreach (var orNode in filtersNode.SelectNodes("Or"))
{
var propertyName = filter.GetAttribute("Property");
var propertyType = filter.GetAttribute("Type");
var propertyValue = filter.GetAttribute("Value");
var validFor = filter.GetAttribute("ValidFor");
var typeToReverse = filter.GetAttribute("TypeToReverse");
XmlElement or = orNode as XmlElement;
if (or == null)
{
continue;
}
List<XmlElement> filterValues = GetNodeFilterValues(xmlFile, type, propertyName);
WriteLine(" filters.Add(new NodeFilter");
// Write initial declarator for the <Or> object, which is just a list of NodePropertyFilters
WriteLine(" filters.Add(new NodeOrFilter");
WriteLine(" {");
WriteLine(string.Format(" Property = \"{0}\",", propertyName));
WriteLine(string.Format(" Type = typeof({0}),", propertyType));
if (!string.IsNullOrWhiteSpace(typeToReverse))
{
WriteLine(string.Format(" TypeToReverse = typeof({0}Querier),", typeToReverse));
}
if (!string.IsNullOrWhiteSpace(validFor))
{
WriteLine(string.Format(" ValidFor = {0},", GetValidForFlags(validFor)));
}
if (propertyValue != null && (filterValues == null || filterValues.Count == 0))
{
WriteLine(string.Format(" Values = new List<object> {{ {0} }},", propertyValue));
}
if (filterValues != null && filterValues.Count > 0)
{
string filterValueType = "object";
if (propertyType == "Enum")
{
WriteLine(" FilterList = new List<NodePropertyFilter> {");
}
WriteLine(string.Format(" Values = new List<object>"));
WriteLine(string.Format(" {{"));
for(int i = 0; i < filterValues.Count; i++)
foreach(var orFilterNode in or.GetElementsByTagName("Filter"))
{
XmlElement orFilter = orFilterNode as XmlElement;
if (orFilter == null)
{
string separator = "";
if (i != filterValues.Count - 1)
{
separator = ",";
}
var filterValue = filterValues[i];
WriteLine(string.Format(" {{ {0} }}{1}", filterValue.InnerText, separator ));
continue;
}
WriteLine(string.Format(" }}"));
// Declaration of Filter node
WriteLine(" new NodePropertyFilter");
// Parse the elements in the <Filter> node into a string, and write
ParseAndWriteFilterNode(xmlFile, type, orFilter, true /*orFilter*/);
// Close out filter object definition
WriteLine(" },");
}
// Close out declaration of the NodeOrFilter
WriteLine(" }");
WriteLine(" });");
}
// Parse each of the top-level <Filter> nodes in <Filters>
foreach (var filterNode in filtersNode.SelectNodes("Filter"))
{
XmlElement filter = filterNode as XmlElement;
if (filter == null)
{
continue;
}
// Start declaration of Filter node
WriteLine(" filters.Add(new NodePropertyFilter");
// Parse the elements in the <Filter> node into a string, and write
ParseAndWriteFilterNode(xmlFile, type, filter);
// Close out filter object definition
WriteLine(" });");
}
// Close out declaration of the Filters object
WriteLine(" return filters;");
WriteLine(" }");
WriteLine(" }");
WriteLine(" }");
}
}
if (smoProperties.Count > 0)
{
WriteLine("");
WriteLine(" public override IEnumerable<NodeSmoProperty> SmoProperties");
WriteLine(" {");
WriteLine(" get");
WriteLine(" {");
WriteLine(" get");
WriteLine(" {");
WriteLine(" var properties = new List<NodeSmoProperty>();");
foreach (var smoPropertiy in smoProperties)
@@ -203,9 +218,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
var propertyName = smoPropertiy.GetAttribute("Name");
var validFor = smoPropertiy.GetAttribute("ValidFor");
WriteLine(" properties.Add(new NodeSmoProperty");
WriteLine(" {");
WriteLine(string.Format(" Name = \"{0}\",", propertyName));
@@ -215,17 +227,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
WriteLine(string.Format(" ValidFor = {0}", GetValidForFlags(validFor)));
}
WriteLine(" });");
}
WriteLine(" return properties;");
WriteLine(" }");
WriteLine(" }");
WriteLine(" }");
}
if (children.Count > 0)
{
WriteLine("");
@@ -276,11 +284,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
WriteLine("");
WriteLine(" internal override Type[] ChildQuerierTypes");
WriteLine(" {");
WriteLine(" get");
WriteLine(" {");
WriteLine(" get");
WriteLine(" {");
if (!string.IsNullOrWhiteSpace(ChildQuerierTypes))
{
Write(" return new [] {");
Write(" return new [] {");
foreach (var typeToRe in allTypes)
{
Write(string.Format(" typeof({0}Querier),", typeToRe));
@@ -289,9 +297,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
}
else
{
Write(" return new Type[0];");
WriteLine(" return new Type[0];");
}
WriteLine(" }");
WriteLine(" }");
WriteLine(" }");
WriteLine("");
@@ -464,14 +472,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
return retElements;
}
public static List<XmlElement> GetNodeFilters(string xmlFile, string parentName)
public static List<XmlElement> GetNodeOrFilters(string xmlFile, string parentName)
{
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
List<XmlElement> retElements = new List<XmlElement>();
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Filter", parentName));
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Or/Filter", parentName));
foreach (var item in nodeList)
{
XmlElement itemAsElement = item as XmlElement;
@@ -483,6 +491,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
return retElements;
}
public static XmlNode GetFiltersNode(string xmlFile, string parentName)
{
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
return doc.SelectSingleNode(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters", parentName));
}
public static List<XmlElement> GetNodeSmoProperties(string xmlFile, string parentName)
{
@@ -503,14 +519,20 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
return retElements;
}
public static List<XmlElement> GetNodeFilterValues(string xmlFile, string parentName, string filterProperty)
public static List<XmlElement> GetNodeFilterValues(string xmlFile, string parentName, string filterProperty, bool orFilter = false)
{
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
List<XmlElement> retElements = new List<XmlElement>();
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Filter[@Property='{1}']/Value", parentName, filterProperty));
var xpath = string.Format(
"/ServerExplorerTree/Node[@Name='{0}']/Filters/{1}Filter[@Property='{2}']/Value",
parentName,
orFilter ? "Or/" : string.Empty,
filterProperty);
XmlNodeList nodeList = doc.SelectNodes(xpath);
foreach (var item in nodeList)
{
XmlElement itemAsElement = item as XmlElement;
@@ -521,4 +543,56 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
}
return retElements;
}
/// <summary>
/// Helper function to parse out and write the contents of a <Filter> node.
/// </summary>
/// <param name="xmlFile">Base xml doc</param>
/// <param name="parentName">name of the parent object in the xml</param>
/// <param name="filter">The filter to be parsed</param>
/// <param name="orFilter">Whether this filter is a sub-node of an <Or> node</param>
public void ParseAndWriteFilterNode(string xmlFile, string parentName, XmlElement filter, bool orFilter = false)
{
// <Or> Filters start at a larger base indentation than <Filter> nodes directly under <Filters>
var indent = orFilter ? " " : " ";
var propertyName = filter.GetAttribute("Property");
var propertyType = filter.GetAttribute("Type");
var propertyValue = filter.GetAttribute("Value");
var validFor = filter.GetAttribute("ValidFor");
var typeToReverse = filter.GetAttribute("TypeToReverse");
List<XmlElement> filterValues = GetNodeFilterValues(xmlFile, parentName, propertyName, orFilter);
// Write out the "meat" of the object definition
WriteLine(indent + "{");
WriteLine(indent + " Property = \"{0}\",", propertyName);
WriteLine(indent + " Type = typeof({0}),", propertyType);
if (!string.IsNullOrWhiteSpace(typeToReverse))
{
WriteLine(indent + " TypeToReverse = typeof({0}Querier),", typeToReverse);
}
if (!string.IsNullOrWhiteSpace(validFor))
{
WriteLine(indent + " ValidFor = {0},", GetValidForFlags(validFor));
}
if (propertyValue != null && (filterValues == null || filterValues.Count == 0))
{
WriteLine(indent + " Values = new List<object> {{ {0} }},", propertyValue);
}
if (filterValues != null && filterValues.Count > 0)
{
WriteLine(indent + " Values = new List<object>");
WriteLine(indent + " {");
for(int i = 0; i < filterValues.Count; i++)
{
string separator = (i != filterValues.Count - 1) ? "," : "";
var filterValue = filterValues[i];
WriteLine(indent + " {{ {0} }}{1}", filterValue.InnerText, separator);
}
WriteLine(indent + " }");
}
}
#>

View File

@@ -0,0 +1,195 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
using NUnit.Framework;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
{
public class NodeFilterTests
{
// Basic PropertyFilter definitions to use in tests
public NodePropertyFilter TemporalFilter =
new NodePropertyFilter
{
Property = "TemporalType",
Type = typeof(Enum),
TypeToReverse = typeof(SqlHistoryTableQuerier),
ValidFor = ValidForFlag.Sql2016|ValidForFlag.Sql2017|ValidForFlag.Sql2019|ValidForFlag.Sql2022|ValidForFlag.AzureV12,
Values = new List<object>
{
{ TableTemporalType.HistoryTable }
}
};
public NodePropertyFilter LedgerHistoryFilter =
new NodePropertyFilter
{
Property = "LedgerType",
Type = typeof(Enum),
TypeToReverse = typeof(SqlHistoryTableQuerier),
ValidFor = ValidForFlag.Sql2022|ValidForFlag.AzureV12,
Values = new List<object>
{
{ LedgerTableType.HistoryTable }
}
};
public NodePropertyFilter SystemObjectFilter =
new NodePropertyFilter
{
Property = "IsSystemObject",
Type = typeof(bool),
Values = new List<object> { 1 },
};
/// <summary>
/// Validates the output of the ToPropertyFilterString for the NodeOrFilter class
/// </summary>
[Test]
public void NodeOrFilterReturnsProperString()
{
var orNode = new NodeOrFilter {
FilterList = new List<NodePropertyFilter> {
TemporalFilter,
LedgerHistoryFilter
}
};
string allFiltersValid = orNode.ToPropertyFilterString(typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2022);
string expectedAllFilters = "((@TemporalType = 1) or (@LedgerType = 1))";
Assert.That(allFiltersValid, Is.EqualTo(expectedAllFilters), "ToPropertyFilterString did not construct the URN filter string as expected for NodeOrFilter");
string sql2016ServerVersion = orNode.ToPropertyFilterString(typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2016);
string expectedSql2016Filters = "((@TemporalType = 1))";
Assert.That(sql2016ServerVersion, Is.EqualTo(expectedSql2016Filters), "ToPropertyFilterString did not construct the URN filter string as expected when excluding filters that aren't valid for the given server type.");
string invalidQuerierType = orNode.ToPropertyFilterString(typeof(SqlTableQuerier), ValidForFlag.Sql2022);
Assert.That(invalidQuerierType, Is.Empty, "ToPropertyFilterString should return empty string, because no given filters match the querier type provided.");
}
/// <summary>
/// Validates the output of GetPropertyFilter with an NodeOrFilter
/// </summary>
[Test]
public void GetPropertyFilterWithNodeOrFilter()
{
var nodeList = new List<INodeFilter> {
new NodeOrFilter {
FilterList = new List<NodePropertyFilter> {
TemporalFilter,
LedgerHistoryFilter
}
}
};
string allFiltersValid = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2022);
string expectedAllFilters = "[((@TemporalType = 1) or (@LedgerType = 1))]";
Assert.That(allFiltersValid, Is.EqualTo(expectedAllFilters), "GetPropertyFilter did not construct the URN filter string as expected");
}
/// <summary>
/// Validates the output of GetPropertyFilter with a list of NodePropertyFilters
/// </summary>
[Test]
public void GetPropertyFilterWithNodePropertyFilters()
{
var nodeList = new List<INodeFilter> {
TemporalFilter,
LedgerHistoryFilter
};
string allFiltersValid = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2022);
string expectedAllFilters = "[(@TemporalType = 1) and (@LedgerType = 1)]";
Assert.That(allFiltersValid, Is.EqualTo(expectedAllFilters), "GetPropertyFilter did not construct the URN filter string as expected");
string sql2016ServerVersion = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2016);
string expectedSql2016Filters = "[(@TemporalType = 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.");
string invalidQuerierType = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlTableQuerier), ValidForFlag.Sql2022);
Assert.That(invalidQuerierType, Is.Empty, "GetPropertyFilter should return empty string, because no given filters match the querier type provided.");
}
/// <summary>
/// Validates the output of GetPropertyFilter with a list of both NodePropertyFilters and NodeOrFilters
/// </summary>
[Test]
public void GetPropertyFilterWithNodePropertyAndNodeOrFilters()
{
var orNode = new NodeOrFilter {
FilterList = new List<NodePropertyFilter> {
TemporalFilter,
LedgerHistoryFilter
}
};
var nodeList = new List<INodeFilter> {
orNode,
SystemObjectFilter
};
string allFiltersValid = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2022);
string expectedAllFilters = "[((@TemporalType = 1) or (@LedgerType = 1)) and (@IsSystemObject = 1)]";
Assert.That(allFiltersValid, Is.EqualTo(expectedAllFilters), "GetPropertyFilter did not construct the URN filter string as expected");
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.");
string invalidQuerierType = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlTableQuerier), ValidForFlag.Sql2022);
string expectedTableQuerierFilters = "[(@IsSystemObject = 1)]";
Assert.That(invalidQuerierType, Is.EqualTo(expectedTableQuerierFilters), "GetPropertyFilter did not construct the URN filter string as expected when excluding filters that don't match the querier type.");
}
/// <summary>
/// Validates the output of GetPropertyFilter with a list of multiple NodeOrFilters with more than 2 filters, and
/// a lone NodePropertyFilter
/// </summary>
[Test]
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 {
FilterList = new List<NodePropertyFilter> {
TemporalFilter,
LedgerHistoryFilter
}
};
var orNode2 = new NodeOrFilter {
FilterList = new List<NodePropertyFilter> {
SystemObjectFilter,
LedgerHistoryFilter,
TemporalFilter
}
};
var nodeList = new List<INodeFilter> {
orNode,
SystemObjectFilter,
orNode2
};
string allFiltersValid = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2022);
string expectedAllFilters = "[((@TemporalType = 1) or (@LedgerType = 1)) and (@IsSystemObject = 1) and ((@IsSystemObject = 1) or (@LedgerType = 1) or (@TemporalType = 1))]";
Assert.That(allFiltersValid, Is.EqualTo(expectedAllFilters), "GetPropertyFilter did not construct the URN filter string as expected");
string sql2016ServerVersion = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlHistoryTableQuerier), ValidForFlag.Sql2016);
string expectedSql2016Filters = "[((@TemporalType = 1)) and (@IsSystemObject = 1) and ((@IsSystemObject = 1) or (@TemporalType = 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.");
string invalidQuerierType = INodeFilter.GetPropertyFilter(nodeList, typeof(SqlTableQuerier), ValidForFlag.Sql2022);
string expectedTableQuerierFilters = "[(@IsSystemObject = 1) and ((@IsSystemObject = 1))]";
Assert.That(invalidQuerierType, Is.EqualTo(expectedTableQuerierFilters), "GetPropertyFilter did not construct the URN filter string as expected when excluding filters that don't match the querier type.");
}
}
}

View File

@@ -9,6 +9,7 @@ using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;
using NUnit.Framework;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
@@ -77,7 +78,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
var tableFactory = new TableValuedFunctionsChildFactory();
var filters = tableFactory.Filters;
Assert.True(filters.ToList().Any(filter => {
return filter.Values.Contains(UserDefinedFunctionType.Table) && filter.Values.Contains(UserDefinedFunctionType.Inline);
var f = filter as NodePropertyFilter;
return f.Values.Contains(UserDefinedFunctionType.Table) && f.Values.Contains(UserDefinedFunctionType.Inline);
}));
}