Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve $count in $filter collection properties #2069

Merged
merged 19 commits into from
May 10, 2021
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
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