Skip to content

Commit

Permalink
Improve $count in $filter collection properties (#2069)
Browse files Browse the repository at this point in the history
  • Loading branch information
KenitoInc authored May 10, 2021
1 parent aa073df commit 011c66c
Show file tree
Hide file tree
Showing 18 changed files with 616 additions and 42 deletions.
1 change: 1 addition & 0 deletions src/Microsoft.OData.Client/Microsoft.OData.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
<Compile Include="..\Microsoft.OData.Core\UriParser\Aggregation\EntitySetAggregateToken.cs" Link="ALinq\UriParser\Aggregation\EntitySetAggregateToken.cs" />
<Compile Include="..\Microsoft.OData.Core\UriParser\Aggregation\AggregateTokenBase.cs" Link="ALinq\UriParser\Aggregation\AggregateTokenBase.cs" />
<Compile Include="..\Microsoft.OData.Core\UriParser\Aggregation\AggregateExpressionToken.cs" Link="ALinq\UriParser\Aggregation\AggregateExpressionToken.cs" />
<Compile Include="..\Microsoft.OData.Core\UriParser\SyntacticAst\CountSegmentToken.cs" Link="ALinq\UriParser\SyntacticAst\CountSegmentToken.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.UriParser.InnerPathTokenBinder.#BindInnerPathSegment(Microsoft.OData.UriParser.InnerPathToken)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])", Scope = "member", Target = "Microsoft.OData.PropertyCacheHandler.#GetProperty(System.String,Microsoft.OData.Edm.IEdmStructuredType)")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Utils", Scope = "type", Target = "Microsoft.OData.Buffers.BufferUtils")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.CountSegmentParser.#CreateCountSegmentToken(Microsoft.OData.UriParser.QueryToken)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.SelectExpandOptionParser.#BuildSelectTermToken(Microsoft.OData.UriParser.PathSegmentToken,System.String)")]

3 changes: 3 additions & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ internal sealed class TextRes {
internal const string UriQueryExpressionParser_OpenParenExpected = "UriQueryExpressionParser_OpenParenExpected";
internal const string UriQueryExpressionParser_CloseParenOrCommaExpected = "UriQueryExpressionParser_CloseParenOrCommaExpected";
internal const string UriQueryExpressionParser_CloseParenOrOperatorExpected = "UriQueryExpressionParser_CloseParenOrOperatorExpected";
internal const string UriQueryExpressionParser_IllegalQueryOptioninDollarCount = "UriQueryExpressionParser_IllegalQueryOptioninDollarCount";
internal const string UriQueryExpressionParser_CannotCreateStarTokenFromNonStar = "UriQueryExpressionParser_CannotCreateStarTokenFromNonStar";
internal const string UriQueryExpressionParser_RangeVariableAlreadyDeclared = "UriQueryExpressionParser_RangeVariableAlreadyDeclared";
internal const string UriQueryExpressionParser_AsExpected = "UriQueryExpressionParser_AsExpected";
Expand Down Expand Up @@ -632,6 +633,7 @@ internal sealed class TextRes {
internal const string MetadataBinder_LeftOperandNotSingleValue = "MetadataBinder_LeftOperandNotSingleValue";
internal const string MetadataBinder_RightOperandNotCollectionValue = "MetadataBinder_RightOperandNotCollectionValue";
internal const string MetadataBinder_PropertyAccessSourceNotSingleValue = "MetadataBinder_PropertyAccessSourceNotSingleValue";
internal const string MetadataBinder_CountSegmentNextTokenNotCollectionValue = "MetadataBinder_CountSegmentNextTokenNotCollectionValue";
internal const string MetadataBinder_IncompatibleOperandsError = "MetadataBinder_IncompatibleOperandsError";
internal const string MetadataBinder_IncompatibleOperandError = "MetadataBinder_IncompatibleOperandError";
internal const string MetadataBinder_UnknownFunction = "MetadataBinder_UnknownFunction";
Expand Down Expand Up @@ -711,6 +713,7 @@ internal sealed class TextRes {
internal const string UriSelectParser_InvalidLevelsOption = "UriSelectParser_InvalidLevelsOption";
internal const string UriSelectParser_SystemTokenInSelectExpand = "UriSelectParser_SystemTokenInSelectExpand";
internal const string UriParser_MissingExpandOption = "UriParser_MissingExpandOption";
internal const string UriParser_EmptyParenthesis = "UriParser_EmptyParenthesis";
internal const string UriParser_MissingSelectOption = "UriParser_MissingSelectOption";
internal const string UriParser_RelativeUriMustBeRelative = "UriParser_RelativeUriMustBeRelative";
internal const string UriParser_NeedServiceRootForThisOverload = "UriParser_NeedServiceRootForThisOverload";
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.txt
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ UriQueryExpressionParser_ExpressionExpected=Expression expected at position {0}
UriQueryExpressionParser_OpenParenExpected='(' expected at position {0} in '{1}'.
UriQueryExpressionParser_CloseParenOrCommaExpected=')' or ',' expected at position {0} in '{1}'.
UriQueryExpressionParser_CloseParenOrOperatorExpected=')' or operator expected at position {0} in '{1}'.
UriQueryExpressionParser_IllegalQueryOptioninDollarCount=Only $filter and $search query options are allowed within $count.
UriQueryExpressionParser_CannotCreateStarTokenFromNonStar=Expecting a Star token but got: '{0}'.
UriQueryExpressionParser_RangeVariableAlreadyDeclared=The range variable '{0}' has already been declared.
UriQueryExpressionParser_AsExpected='as' expected at position {0} in '{1}'.
Expand Down Expand Up @@ -728,6 +729,7 @@ MetadataBinder_UnaryOperatorOperandNotSingleValue=The operand for a unary operat
MetadataBinder_LeftOperandNotSingleValue=The left operand for the IN operation is not a single value. IN operations require the left operand to be a single value and the right operand to be a collection value.
MetadataBinder_RightOperandNotCollectionValue=The right operand for the IN operation is not a collection value. IN operations require the left operand to be a single value and the right operand to be a collection value.
MetadataBinder_PropertyAccessSourceNotSingleValue=The parent value for a property access of a property '{0}' is not a single value. Property access can only be applied to a single value.
MetadataBinder_CountSegmentNextTokenNotCollectionValue=The next token in a CountSegmentNode must be a collection.
MetadataBinder_IncompatibleOperandsError=A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'.
MetadataBinder_IncompatibleOperandError=A unary operator with an incompatible type was detected. Found operand type '{0}' for operator kind '{1}'.
MetadataBinder_UnknownFunction=An unknown function with name '{0}' was found. This may also be a function import or a key lookup on a navigation property, which is not allowed.
Expand Down Expand Up @@ -819,6 +821,7 @@ UriSelectParser_InvalidCountOption=Count option must be a boolean value, it is s
UriSelectParser_InvalidLevelsOption=Levels option must be a non-negative integer or 'max', it is set to '{0}' instead.
UriSelectParser_SystemTokenInSelectExpand=Found system token '{0}' in select or expand clause '{1}'.
UriParser_MissingExpandOption=Missing expand option on navigation property '{0}'. If a parenthesis expression follows an expanded navigation property, then at least one expand option must be provided.
UriParser_EmptyParenthesis=Empty parenthesis not allowed.
UriParser_MissingSelectOption=Missing select option on property '{0}'. If a parenthesis expression follows a selected property, then at least one query option must be provided.
UriParser_RelativeUriMustBeRelative=Parameter 'relativeUri' must be a relative Uri if serviceRoot is not specified.
UriParser_NeedServiceRootForThisOverload=A service root URI must be provided to the ODataUriParser in order to use this method.
Expand Down
24 changes: 24 additions & 0 deletions src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5212,6 +5212,14 @@ internal static string UriQueryExpressionParser_CloseParenOrOperatorExpected(obj
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_CloseParenOrOperatorExpected, p0, p1);
}

/// <summary>
/// A string like "Only $filter and $search query options are allowed within $count."
/// </summary>
internal static string UriQueryExpressionParser_IllegalQueryOptioninDollarCount()
{
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_IllegalQueryOptioninDollarCount);
}

/// <summary>
/// A string like "Expecting a Star token but got: '{0}'."
/// </summary>
Expand Down Expand Up @@ -5495,6 +5503,14 @@ internal static string MetadataBinder_PropertyAccessSourceNotSingleValue(object
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_PropertyAccessSourceNotSingleValue, p0);
}

/// <summary>
/// A string like "The next token in a CountSegmentNode must be a collection."
/// </summary>
internal static string MetadataBinder_CountSegmentNextTokenNotCollectionValue()
{
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_CountSegmentNextTokenNotCollectionValue);
}

/// <summary>
/// A string like "A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'."
/// </summary>
Expand Down Expand Up @@ -6196,6 +6212,14 @@ internal static string UriParser_MissingExpandOption(object p0)
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_MissingExpandOption, p0);
}

/// <summary>
/// A string like "Empty parenthesis not allowed."
/// </summary>
internal static string UriParser_EmptyParenthesis()
{
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_EmptyParenthesis);
}

/// <summary>
/// A string like "Missing select option on property '{0}'. If a parenthesis expression follows a selected property, then at least one query option must be provided."
/// </summary>
Expand Down
77 changes: 77 additions & 0 deletions src/Microsoft.OData.Core/UriParser/Binders/CountSegmentBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//---------------------------------------------------------------------
// <copyright file="CountSegmentBinder.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData.UriParser
{
using ODataErrorStrings = Microsoft.OData.Strings;

/// <summary>
/// Class that knows how to bind a Count segment token.
/// </summary>
internal sealed class CountSegmentBinder
{
/// <summary>
/// Method to use to visit the token tree and bind the tokens recursively.
/// </summary>
private readonly MetadataBinder.QueryTokenVisitor bindMethod;

/// <summary>
/// State to use for binding.
/// </summary>
private readonly BindingState state;

/// <summary>
/// Constructs a CountSegmentBinder object.
/// </summary>
/// <param name="bindMethod">Method to use to visit the token tree and bind the tokens recursively.</param>
/// <param name="state">State of the metadata binding.</param>
internal CountSegmentBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state)
{
this.bindMethod = bindMethod;
this.state = state;
}

/// <summary>
/// Binds an Count segment token.
/// </summary>
/// <param name="countSegmentToken">The Count segment token to bind.</param>
/// <param name="state">State of the metadata binding.</param>
/// <returns>The bound Count segment token.</returns>
internal QueryNode BindCountSegment(CountSegmentToken countSegmentToken)
{
ExceptionUtils.CheckArgumentNotNull(countSegmentToken, "countSegmentToken");

QueryNode source = this.bindMethod(countSegmentToken.NextToken);
CollectionNode node = source as CollectionNode;

if(node == null)
{
throw new ODataException(ODataErrorStrings.MetadataBinder_CountSegmentNextTokenNotCollectionValue());
}

FilterClause filterClause = null;
SearchClause searchClause = null;

BindingState innerBindingState = new BindingState(state.Configuration);
innerBindingState.ImplicitRangeVariable = NodeFactory.CreateParameterNode(ExpressionConstants.It, node);
MetadataBinder binder = new MetadataBinder(innerBindingState);

if (countSegmentToken.FilterOption != null)
{
FilterBinder filterBinder = new FilterBinder(binder.Bind, innerBindingState);
filterClause = filterBinder.BindFilter(countSegmentToken.FilterOption);
}

if(countSegmentToken.SearchOption != null)
{
SearchBinder searchBinder = new SearchBinder(binder.Bind);
searchClause = searchBinder.BindSearch(countSegmentToken.SearchOption);
}

return new CountNode(node, filterClause, searchClause);
}
}
}
14 changes: 14 additions & 0 deletions src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ protected internal QueryNode Bind(QueryToken token)
case QueryTokenKind.In:
result = this.BindIn((InToken)token);
break;
case QueryTokenKind.CountSegment:
result = this.BindCountSegment((CountSegmentToken)token);
break;
default:
throw new ODataException(ODataErrorStrings.MetadataBinder_UnsupportedQueryTokenKind(token.Kind));
}
Expand Down Expand Up @@ -370,5 +373,16 @@ protected virtual QueryNode BindIn(InToken inToken)
InBinder inBinder = new InBinder(InBinderMethod);
return inBinder.BindInOperator(inToken, this.BindingState);
}

/// <summary>
/// Binds a CountSegment token.
/// </summary>
/// <param name="countSegmentToken">The CountSegment token to bind.</param>
/// <returns>The bound CountSegment token.</returns>
protected virtual QueryNode BindCountSegment(CountSegmentToken countSegmentToken)
{
CountSegmentBinder countSegmentBinder = new CountSegmentBinder(this.Bind, this.BindingState);
return countSegmentBinder.BindCountSegment(countSegmentToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ private FilterClause ParseFilterImplementation(string filter, ODataUriParserConf
ExceptionUtils.CheckArgumentNotNull(filter, "filter");

// Get the syntactic representation of the filter expression
UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier);
UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier, configuration.Resolver.EnableNoDollarQueryOptions);
QueryToken filterToken = expressionParser.ParseFilter(filter);

// Bind it to metadata
Expand Down
Loading

0 comments on commit 011c66c

Please sign in to comment.