Adding filtering support to OE (#2039)

* Init push

* Fixing filters

* Fixing more filters

* Fixing display strings

* Fixing boolean filter

* Adding comments

* Fixing function name

* Making nullables

* Separating filter parsing logic

* Adding tests

* Update src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs

Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>

* Update src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs

Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>

* Update src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* Update src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* Update src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* Adding comments

* Fixing whitespace

* Adding more  comments and changing to IEnumerable

* Fixing code comments

* Fixing tests adding more filters

---------

Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
This commit is contained in:
Aasim Khan
2023-05-05 13:22:42 -07:00
committed by GitHub
parent 1b5f774741
commit 46e6b484a3
18 changed files with 2013 additions and 81 deletions

View File

@@ -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<object>
{
Property = "TemporalType",
Type = typeof(Enum),
TypeToReverse = typeof(SqlHistoryTableQuerier),
ValidFor = ValidForFlag.Sql2016 | ValidForFlag.Sql2017 | ValidForFlag.Sql2019 | ValidForFlag.Sql2022OrHigher | ValidForFlag.AzureV12,
Values = new List<object>
{
{ 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<object>
{
Property = "LedgerType",
Type = typeof(Enum),
TypeToReverse = typeof(SqlHistoryTableQuerier),
ValidFor = ValidForFlag.Sql2022OrHigher | ValidForFlag.AzureV12,
Values = new List<object>
{
{ LedgerTableType.HistoryTable }
}
};
};
public NodePropertyFilter SystemObjectFilter =
new NodePropertyFilter
{
Property = "IsSystemObject",
Type = typeof(bool),
Values = new List<object> { 1 },
};
{
Property = "IsSystemObject",
Type = typeof(bool),
Values = new List<object> { 1 },
};
/// <summary>
/// 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<NodePropertyFilter> {
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<NodePropertyFilter> {
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<NodePropertyFilter> {
TemporalFilter,
LedgerHistoryFilter
}
};
var orNode2 = new NodeOrFilter {
var orNode2 = new NodeOrFilter
{
FilterList = new List<NodePropertyFilter> {
SystemObjectFilter,
LedgerHistoryFilter,
@@ -200,7 +204,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
TemporalFilter,
LedgerHistoryFilter
};
var nodeList = new List<INodeFilter> {
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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { "invalid value" },
FilterType = FilterType.EQUALS,
IsDateTime = true
}
};
Assert.Throws<FormatException>(() => 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { new string[] {"invalid value", "2021-01-02"}},
FilterType = FilterType.BETWEEN,
IsDateTime = true
}
};
Assert.Throws<FormatException>(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when value array contains invalid date value for between operator");
filterList = new List<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> {"2021-01-02"},
FilterType = FilterType.BETWEEN,
IsDateTime = true
}
};
Assert.Throws<InvalidCastException>(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when only one date value is provided for between operator");
filterList = new List<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "CreateDate",
Type = typeof(string),
ValidFor = ValidForFlag.All,
Values = new List<object> { new string[] {"2021-01-02"}},
FilterType = FilterType.BETWEEN,
IsDateTime = true
}
};
Assert.Throws<IndexOutOfRangeException>(() => 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { "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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { "invalid value" },
FilterType = FilterType.EQUALS,
IsDateTime = false
}
};
Assert.Throws<FormatException>(() => 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { new object[] {"invalid value", 200}},
FilterType = FilterType.BETWEEN,
IsDateTime = false
}
};
Assert.Throws<FormatException>(() => 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<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> {200},
FilterType = FilterType.BETWEEN,
IsDateTime = false
}
};
Assert.Throws<InvalidCastException>(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when a single value is passed for between operator");
filterList = new List<NodePropertyFilter>
{
new NodePropertyFilter()
{
Property = "RowCount",
Type = typeof(int),
ValidFor = ValidForFlag.All,
Values = new List<object> { new object[] {200}},
FilterType = FilterType.BETWEEN,
IsDateTime = false
}
};
Assert.Throws<IndexOutOfRangeException>(() => INodeFilter.GetPropertyFilter(filterList, typeof(SqlHistoryTableQuerier), ValidForFlag.All), "Error not thrown when the array contains single value for between operator");
}
}
}

View File

@@ -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<TreeNode>();
object[] populateChildrenArguments = { ItExpr.Is<bool>(x => x == false), ItExpr.IsNull<string>(), new CancellationToken(), ItExpr.IsNull<string>() };
object[] populateChildrenArguments = { ItExpr.Is<bool>(x => x == false), ItExpr.IsNull<string>(), new CancellationToken(), ItExpr.IsNull<string>(), ItExpr.IsNull<IEnumerable<NodeFilter>>() };
mockTreeNode.Protected().Setup("PopulateChildren", populateChildrenArguments);
mockTreeNode.Object.IsAlwaysLeaf = false;