diff --git a/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs b/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs index e8b514a329..434a4ec0d7 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs @@ -128,7 +128,7 @@ internal List QueryOptions /// /// Collection of aggregated property names after applying an aggregate transformation. /// - internal List AggregatedProperties { get; set; } + internal List AggregatedPropertyNames { get; set; } /// /// Marks the fact that a recursive method was entered, and checks that the depth is allowed. diff --git a/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs index 20383df550..d47c8ab536 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs @@ -194,7 +194,7 @@ private QueryNode DetermineParentNode(EndPathToken segmentToken) /// Whether the token represents an aggregated property. private bool IsAggregatedProperty(string identifier) { - return (state.AggregatedProperties != null && state.AggregatedProperties.Contains(identifier)); + return (state.AggregatedPropertyNames != null && state.AggregatedPropertyNames.Contains(identifier)); } } } \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/Extensions/Binders/ApplyBinder.cs b/src/Microsoft.OData.Core/UriParser/Extensions/Binders/ApplyBinder.cs index 1055c351ef..1eb284c7ac 100644 --- a/src/Microsoft.OData.Core/UriParser/Extensions/Binders/ApplyBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Extensions/Binders/ApplyBinder.cs @@ -49,7 +49,7 @@ public ApplyClause BindApply(IEnumerable tokens) var aggregate = BindAggregateToken((AggregateToken)(token)); transformations.Add(aggregate); aggregateStatementsCache = aggregate.Statements; - state.AggregatedProperties = + state.AggregatedPropertyNames = aggregate.Statements.Select(statement => statement.AsAlias).ToList(); break; case QueryTokenKind.AggregateGroupBy: @@ -175,7 +175,7 @@ private GroupByTransformationNode BindGroupByToken(GroupByToken token) { aggregate = BindAggregateToken((AggregateToken)token.Child); aggregateStatementsCache = ((AggregateTransformationNode)aggregate).Statements; - state.AggregatedProperties = + state.AggregatedPropertyNames = aggregateStatementsCache.Select(statement => statement.AsAlias).ToList(); } else diff --git a/src/Microsoft.OData.Core/UriParser/Extensions/SemanticAst/ApplyClause.cs b/src/Microsoft.OData.Core/UriParser/Extensions/SemanticAst/ApplyClause.cs index 6dbb7db0b5..66ad36b051 100644 --- a/src/Microsoft.OData.Core/UriParser/Extensions/SemanticAst/ApplyClause.cs +++ b/src/Microsoft.OData.Core/UriParser/Extensions/SemanticAst/ApplyClause.cs @@ -71,6 +71,16 @@ internal string GetContextUri() return CreatePropertiesUriSegment(lastGroupByPropertyNodes, lastAggregateStatements); } + internal List GetLastAggregatedPropertyNames() + { + if (lastAggregateStatements != null) + { + return lastAggregateStatements.Select(statement => statement.AsAlias).ToList(); + } + + return null; + } + private string CreatePropertiesUriSegment( IEnumerable groupByPropertyNodes, IEnumerable aggregateStatements) diff --git a/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs b/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs index e195e9d52e..3b297a78b8 100644 --- a/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs +++ b/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs @@ -294,7 +294,7 @@ public string ParseDeltaToken() /// Type that the filter clause refers to. /// Navigation source that the elements being filtered are from. /// A representing the metadata bound filter expression. - private static FilterClause ParseFilterImplementation(string filter, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) + private FilterClause ParseFilterImplementation(string filter, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) { ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); ExceptionUtils.CheckArgumentNotNull(elementType, "elementType"); @@ -308,6 +308,11 @@ private static FilterClause ParseFilterImplementation(string filter, ODataUriPar BindingState state = new BindingState(configuration); state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(elementType.ToTypeReference(), navigationSource); state.RangeVariables.Push(state.ImplicitRangeVariable); + if (applyClause != null) + { + state.AggregatedPropertyNames = applyClause.GetLastAggregatedPropertyNames(); + } + MetadataBinder binder = new MetadataBinder(state); FilterBinder filterBinder = new FilterBinder(binder.Bind, state); FilterClause boundNode = filterBinder.BindFilter(filterToken); @@ -381,7 +386,7 @@ private static SelectExpandClause ParseSelectAndExpandImplementation(string sele /// Type that the orderby clause refers to. /// NavigationSource that the elements are from. /// An representing the metadata bound orderby expression. - private static OrderByClause ParseOrderByImplementation(string orderBy, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) + private OrderByClause ParseOrderByImplementation(string orderBy, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) { ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); ExceptionUtils.CheckArgumentNotNull(configuration.Model, "model"); @@ -396,6 +401,11 @@ private static OrderByClause ParseOrderByImplementation(string orderBy, ODataUri BindingState state = new BindingState(configuration); state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(elementType.ToTypeReference(), navigationSource); state.RangeVariables.Push(state.ImplicitRangeVariable); + if (applyClause != null) + { + state.AggregatedPropertyNames = applyClause.GetLastAggregatedPropertyNames(); + } + MetadataBinder binder = new MetadataBinder(state); OrderByBinder orderByBinder = new OrderByBinder(binder.Bind); OrderByClause orderByClause = orderByBinder.BindOrderBy(state, orderByQueryTokens); diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs index ac06421af2..4ee86f1f72 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs @@ -1061,6 +1061,77 @@ public void ActionsThrowOnClosedTypeInFilter() parseWithAction.ShouldThrow().WithMessage(ODataErrorStrings.MetadataBinder_PropertyNotDeclared("Fully.Qualified.Namespace.Person", "Move")); } + [Fact] + public void AggregatedPropertyTreatedAsOpenProperty() + { + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet(), + new Dictionary() + { + {"$filter", "Total"}, + {"$apply", "aggregate(FavoriteNumber with sum as Total)"} + }); + odataQueryOptionParser.ParseApply(); + var filterClause = odataQueryOptionParser.ParseFilter(); + filterClause.Expression.ShouldBeSingleValueOpenPropertyAccessQueryNode("Total"); + } + + [Fact] + public void AggregatedPropertiesTreatedAsOpenProperty() + { + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet(), + new Dictionary() + { + {"$filter", "Total ge 10 and Max le 2"}, + {"$apply", "aggregate(FavoriteNumber with sum as Total, StockQuantity with max as Max)"} + }); + odataQueryOptionParser.ParseApply(); + var filterClause = odataQueryOptionParser.ParseFilter(); + var binaryOperatorNode = filterClause.Expression.ShouldBeBinaryOperatorNode(BinaryOperatorKind.And).And; + var leftBinaryOperatorNode = + binaryOperatorNode.Left.ShouldBeBinaryOperatorNode(BinaryOperatorKind.GreaterThanOrEqual).And; + var rightBinaryOperatorNode = + binaryOperatorNode.Right.ShouldBeBinaryOperatorNode(BinaryOperatorKind.LessThanOrEqual).And; + leftBinaryOperatorNode.Left.As().Source.ShouldBeSingleValueOpenPropertyAccessQueryNode("Total"); + rightBinaryOperatorNode.Left.As().Source.ShouldBeSingleValueOpenPropertyAccessQueryNode("Max"); + } + + [Fact] + public void AggregatedPropertyTreatedAsOpenPropertyInOrderBy() + { + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet(), + new Dictionary() + { + {"$orderby", "Total asc"}, + {"$apply", "aggregate(FavoriteNumber with sum as Total)"} + }); + odataQueryOptionParser.ParseApply(); + var orderByClause = odataQueryOptionParser.ParseOrderBy(); + orderByClause.Direction.Should().Be(OrderByDirection.Ascending); + orderByClause.Expression.ShouldBeSingleValueOpenPropertyAccessQueryNode("Total"); + } + + [Fact] + public void AggregatedPropertiesTreatedAsOpenPropertyInOrderBy() + { + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet(), + new Dictionary() + { + {"$orderby", "Total asc, Max desc"}, + {"$apply", "aggregate(FavoriteNumber with sum as Total, StockQuantity with max as Max)"} + }); + odataQueryOptionParser.ParseApply(); + var orderByClause = odataQueryOptionParser.ParseOrderBy(); + orderByClause.Direction.Should().Be(OrderByDirection.Ascending); + orderByClause.Expression.ShouldBeSingleValueOpenPropertyAccessQueryNode("Total"); + orderByClause = orderByClause.ThenBy; + orderByClause.Direction.Should().Be(OrderByDirection.Descending); + orderByClause.Expression.ShouldBeSingleValueOpenPropertyAccessQueryNode("Max"); + } + [Fact] public void ActionsThrowOnClosedInOrderby() { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/EndPathBinderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/EndPathBinderTests.cs index fe0f480ac0..0dcd98937f 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/EndPathBinderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/Binders/EndPathBinderTests.cs @@ -254,7 +254,7 @@ public void ShouldNotThrowIfTypeNotOpenButAggregateApplied() SingleValueNode parentNode = new EntityRangeVariableReferenceNode("a", new EntityRangeVariable("a", HardCodedTestModel.GetPersonTypeReference(), entityCollectionNode)); var state = new BindingState(this.configuration); - state.AggregatedProperties = new List { "Color" }; + state.AggregatedPropertyNames = new List { "Color" }; var metadataBinder = new MetadataBinder(state); var endPathBinder = new EndPathBinder(metadataBinder.Bind, state); var propertyNode = endPathBinder.GeneratePropertyAccessQueryForOpenType(