Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.OData.ModelBuilder" Version="1.0.8" />
<PackageReference Include="Microsoft.OData.Core" Version="7.12.5" />
<PackageReference Include="Microsoft.OData.Edm" Version="7.12.5" />
<PackageReference Include="Microsoft.OData.Core" Version="7.12.2" />
<PackageReference Include="Microsoft.OData.Edm" Version="7.12.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Spatial" Version="7.12.5" />
<PackageReference Include="Microsoft.Spatial" Version="7.12.2" />
</ItemGroup>

Expand Down
17 changes: 15 additions & 2 deletions src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7570,6 +7570,11 @@
A value that corresponds to allowing 'All' function in $filter.
</summary>
</member>
<member name="F:Microsoft.AspNetCore.OData.Query.AllowedFunctions.MatchesPattern">
<summary>
A value that corresponds to allowing 'MatchesPattern' function in $filter.
</summary>
</member>
<member name="F:Microsoft.AspNetCore.OData.Query.AllowedFunctions.AllStringFunctions">
<summary>
A value that corresponds to allowing all string related functions in $filter.
Expand Down Expand Up @@ -8074,7 +8079,7 @@
<item>
<term>String related:</term>
<description>contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim,
concat e.g. ~/Customers?$filter=length(CompanyName) eq 19</description>
concat, matchesPattern e.g. ~/Customers?$filter=length(CompanyName) eq 19</description>
</item>
<item>
<term>DateTime related:</term>
Expand Down Expand Up @@ -9102,6 +9107,14 @@
<param name="context">The query binder context.</param>
<returns>The LINQ <see cref="T:System.Linq.Expressions.Expression"/> created.</returns>
</member>
<member name="M:Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.BindMatchesPattern(Microsoft.OData.UriParser.SingleValueFunctionCallNode,Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext)">
<summary>
Binds a 'matchesPattern' function to create a LINQ <see cref="T:System.Linq.Expressions.Expression"/>.
</summary>
<param name="node">The query node to bind.</param>
<param name="context">The query binder context.</param>
<returns>The LINQ <see cref="T:System.Linq.Expressions.Expression"/> created.</returns>
</member>
<member name="M:Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.BindDateRelatedProperty(Microsoft.OData.UriParser.SingleValueFunctionCallNode,Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext)">
<summary>
Binds date related functions to create a LINQ <see cref="T:System.Linq.Expressions.Expression"/>.
Expand Down Expand Up @@ -11577,7 +11590,7 @@

The allowed functions include the following:

String related: contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim, concat
String related: contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim, concat, matchesPattern

e.g. ~/Customers?$filter=length(CompanyName) eq 19

Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.AspNetCore.OData/Query/AllowedFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,15 @@ public enum AllowedFunctions
/// </summary>
All = 0x10000000,

/// <summary>
/// A value that corresponds to allowing 'MatchesPattern' function in $filter.
/// </summary>
MatchesPattern = 0x20000000,

/// <summary>
/// A value that corresponds to allowing all string related functions in $filter.
/// </summary>
AllStringFunctions = StartsWith | EndsWith | Contains | Length | IndexOf | Concat | Substring | ToLower | ToUpper | Trim,
AllStringFunctions = StartsWith | EndsWith | Contains | Length | IndexOf | Concat | Substring | ToLower | ToUpper | Trim | MatchesPattern,

/// <summary>
/// A value that corresponds to allowing all datetime related functions in $filter.
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.OData/Query/ClrCanonicalFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.OData.Edm;

namespace Microsoft.AspNetCore.OData.Query
Expand All @@ -36,6 +37,7 @@ internal class ClrCanonicalFunctions
internal const string ToupperFunctionName = "toupper";
internal const string TrimFunctionName = "trim";
internal const string ConcatFunctionName = "concat";
internal const string MatchesPatternFunctionName = "matchesPattern";
internal const string YearFunctionName = "year";
internal const string MonthFunctionName = "month";
internal const string DayFunctionName = "day";
Expand Down Expand Up @@ -66,6 +68,7 @@ internal class ClrCanonicalFunctions
public static readonly MethodInfo ToUpper = MethodOf(_ => _defaultString.ToUpper());
public static readonly MethodInfo Trim = MethodOf(_ => _defaultString.Trim());
public static readonly MethodInfo Concat = MethodOf(_ => String.Concat(default(string), default(string)));
public static readonly MethodInfo MatchesMattern = MethodOf(_ => Regex.IsMatch(default(string), default(string), default(RegexOptions)));

// math functions
public static readonly MethodInfo CeilingOfDouble = MethodOf(_ => Math.Ceiling(default(double)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public AllowedQueryOptions AllowedQueryOptions
/// <item>
/// <term>String related:</term>
/// <description>contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim,
/// concat e.g. ~/Customers?$filter=length(CompanyName) eq 19</description>
/// concat, matchesPattern e.g. ~/Customers?$filter=length(CompanyName) eq 19</description>
/// </item>
/// <item>
/// <term>DateTime related:</term>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Microsoft.AspNetCore.OData.Abstracts;
using Microsoft.AspNetCore.OData.Common;
Expand Down Expand Up @@ -199,6 +200,9 @@ public virtual Expression BindSingleValueFunctionCallNode(SingleValueFunctionCal
case ClrCanonicalFunctions.ConcatFunctionName:
return BindConcat(node);

case ClrCanonicalFunctions.MatchesPatternFunctionName:
return BindMatchesPattern(node);

case ClrCanonicalFunctions.YearFunctionName:
case ClrCanonicalFunctions.MonthFunctionName:
case ClrCanonicalFunctions.DayFunctionName:
Expand Down Expand Up @@ -545,6 +549,21 @@ private Expression BindConcat(SingleValueFunctionCallNode node)
return ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.Concat, QuerySettings, arguments);
}

private Expression BindMatchesPattern(SingleValueFunctionCallNode node)
{
Contract.Assert("matchesPattern" == node.Name);

Expression[] arguments = BindArguments(node.Parameters);
ValidateAllStringArguments(node.Name, arguments);

Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));

//add argument that must be ECMAScript compatible regex
arguments = new[] { arguments[0], arguments[1], Expression.Constant(RegexOptions.ECMAScript) };

return ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.MatchesMattern, QuerySettings, arguments);
}

private Expression BindTrim(SingleValueFunctionCallNode node)
{
Contract.Assert("trim" == node.Name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.OData.Common;
using Microsoft.AspNetCore.OData.Edm;
using Microsoft.OData;
Expand Down Expand Up @@ -68,6 +69,9 @@ public virtual Expression BindSingleValueFunctionCallNode(SingleValueFunctionCal
case ClrCanonicalFunctions.ConcatFunctionName:
return BindConcat(node, context);

case ClrCanonicalFunctions.MatchesPatternFunctionName:
return BindMatchesPattern(node, context);

case ClrCanonicalFunctions.YearFunctionName:
case ClrCanonicalFunctions.MonthFunctionName:
case ClrCanonicalFunctions.DayFunctionName:
Expand Down Expand Up @@ -337,6 +341,27 @@ protected virtual Expression BindConcat(SingleValueFunctionCallNode node, QueryB
return ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.Concat, context.QuerySettings, arguments);
}

/// <summary>
/// Binds a 'matchesPattern' function to create a LINQ <see cref="Expression"/>.
/// </summary>
/// <param name="node">The query node to bind.</param>
/// <param name="context">The query binder context.</param>
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
protected virtual Expression BindMatchesPattern(SingleValueFunctionCallNode node, QueryBinderContext context)
{
CheckArgumentNull(node, context, "matchesPattern");

Expression[] arguments = BindArguments(node.Parameters, context);
ValidateAllStringArguments(node.Name, arguments);

Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));

//add argument that must be ECMAScript compatible regex
arguments = new[] { arguments[0], arguments[1], Expression.Constant(RegexOptions.ECMAScript) };

return ExpressionBinderHelper.MakeFunctionCall(ClrCanonicalFunctions.MatchesMattern, context.QuerySettings, arguments);
}

/// <summary>
/// Binds date related functions to create a LINQ <see cref="Expression"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ private static AllowedFunctions ToODataFunction(string functionName)
case ClrCanonicalFunctions.LengthFunctionName:
result = AllowedFunctions.Length;
break;
case ClrCanonicalFunctions.MatchesPatternFunctionName:
result = AllowedFunctions.MatchesPattern;
break;
case ClrCanonicalFunctions.MinuteFunctionName:
result = AllowedFunctions.Minute;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public AllowedArithmeticOperators AllowedArithmeticOperators
///
/// The allowed functions include the following:
///
/// String related: contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim, concat
/// String related: contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim, concat, matchesPattern
///
/// e.g. ~/Customers?$filter=length(CompanyName) eq 19
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1110,9 +1110,9 @@ FlagsAttribute(),
public enum Microsoft.AspNetCore.OData.Query.AllowedFunctions : int {
All = 268435456
AllDateTimeFunctions = 7010304
AllFunctions = 535494655
AllFunctions = 1072365567
AllMathFunctions = 58720256
AllStringFunctions = 1023
AllStringFunctions = 536871935
Any = 134217728
Cast = 1024
Ceiling = 33554432
Expand All @@ -1127,6 +1127,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedFunctions : int {
IndexOf = 16
IsOf = 67108864
Length = 8
MatchesPattern = 536870912
Minute = 524288
Month = 8192
None = 0
Expand Down Expand Up @@ -2712,6 +2713,7 @@ public abstract class Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder {
public virtual System.Linq.Expressions.Expression BindInNode (Microsoft.OData.UriParser.InNode inNode, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindIsOf (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindLength (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindMatchesPattern (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
public virtual System.Linq.Expressions.Expression BindNavigationPropertyNode (Microsoft.OData.UriParser.QueryNode sourceNode, Microsoft.OData.Edm.IEdmNavigationProperty navigationProperty, string propertyPath, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindNow (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
public virtual System.Linq.Expressions.Expression BindPropertyAccessQueryNode (Microsoft.OData.UriParser.SingleValuePropertyAccessNode propertyAccessNode, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1110,9 +1110,9 @@ FlagsAttribute(),
public enum Microsoft.AspNetCore.OData.Query.AllowedFunctions : int {
All = 268435456
AllDateTimeFunctions = 7010304
AllFunctions = 535494655
AllFunctions = 1072365567
AllMathFunctions = 58720256
AllStringFunctions = 1023
AllStringFunctions = 536871935
Any = 134217728
Cast = 1024
Ceiling = 33554432
Expand All @@ -1127,6 +1127,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedFunctions : int {
IndexOf = 16
IsOf = 67108864
Length = 8
MatchesPattern = 536870912
Minute = 524288
Month = 8192
None = 0
Expand Down Expand Up @@ -2712,6 +2713,7 @@ public abstract class Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder {
public virtual System.Linq.Expressions.Expression BindInNode (Microsoft.OData.UriParser.InNode inNode, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindIsOf (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindLength (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindMatchesPattern (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
public virtual System.Linq.Expressions.Expression BindNavigationPropertyNode (Microsoft.OData.UriParser.QueryNode sourceNode, Microsoft.OData.Edm.IEdmNavigationProperty navigationProperty, string propertyPath, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindNow (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
public virtual System.Linq.Expressions.Expression BindPropertyAccessQueryNode (Microsoft.OData.UriParser.SingleValuePropertyAccessNode propertyAccessNode, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1110,9 +1110,9 @@ FlagsAttribute(),
public enum Microsoft.AspNetCore.OData.Query.AllowedFunctions : int {
All = 268435456
AllDateTimeFunctions = 7010304
AllFunctions = 535494655
AllFunctions = 1072365567
AllMathFunctions = 58720256
AllStringFunctions = 1023
AllStringFunctions = 536871935
Any = 134217728
Cast = 1024
Ceiling = 33554432
Expand All @@ -1127,6 +1127,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedFunctions : int {
IndexOf = 16
IsOf = 67108864
Length = 8
MatchesPattern = 536870912
Minute = 524288
Month = 8192
None = 0
Expand Down Expand Up @@ -2712,6 +2713,7 @@ public abstract class Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder {
public virtual System.Linq.Expressions.Expression BindInNode (Microsoft.OData.UriParser.InNode inNode, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindIsOf (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindLength (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindMatchesPattern (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
public virtual System.Linq.Expressions.Expression BindNavigationPropertyNode (Microsoft.OData.UriParser.QueryNode sourceNode, Microsoft.OData.Edm.IEdmNavigationProperty navigationProperty, string propertyPath, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
protected virtual System.Linq.Expressions.Expression BindNow (Microsoft.OData.UriParser.SingleValueFunctionCallNode node, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
public virtual System.Linq.Expressions.Expression BindPropertyAccessQueryNode (Microsoft.OData.UriParser.SingleValuePropertyAccessNode propertyAccessNode, Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static TheoryDataSet<AllowedFunctions> AllStringFunctionsData
AllowedFunctions.ToLower,
AllowedFunctions.ToUpper,
AllowedFunctions.Trim,
AllowedFunctions.MatchesPattern,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,25 @@ public void StringFunctions_StringConcat()
InvokeFiltersAndVerify(filters, new Product { ProductName = "Food" }, (true, true));
}

[Fact]
public void StringFunctions_StringMatchesPattern()
{
// Arrange & Act & Assert
var filters = BindFilterAndVerify<Product>(
"matchesPattern(ProductName, 'A\\wc')",
"$it => $it.ProductName.IsMatch(\"A\\wc\", ECMAScript)",
NotTesting);

// Arrange & Act & Assert
InvokeFiltersAndThrows(filters, new Product { ProductName = null }, (typeof(ArgumentNullException), false));

InvokeFiltersAndVerify(filters, new Product { ProductName = "Abcd" }, (true, true));

InvokeFiltersAndVerify(filters, new Product { ProductName = "Abd" }, (false, false));

InvokeFiltersAndVerify(filters, new Product { ProductName = "Aθd" }, (false, false)); // ECMAScript has strict matching of \w
}

[Fact]
public void StringFunctions_RecursiveMethodCall()
{
Expand Down
Loading