Skip to content

Commit

Permalink
Support for filter and orderby Aggregated properties
Browse files Browse the repository at this point in the history
  • Loading branch information
VikingsFan committed Jan 28, 2016
1 parent c8e2acb commit e1f4ff0
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ internal List<CustomQueryOptionToken> QueryOptions
/// <summary>
/// Collection of aggregated property names after applying an aggregate transformation.
/// </summary>
internal List<string> AggregatedProperties { get; set; }
internal List<string> AggregatedPropertyNames { get; set; }

/// <summary>
/// Marks the fact that a recursive method was entered, and checks that the depth is allowed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private QueryNode DetermineParentNode(EndPathToken segmentToken)
/// <returns>Whether the token represents an aggregated property.</returns>
private bool IsAggregatedProperty(string identifier)
{
return (state.AggregatedProperties != null && state.AggregatedProperties.Contains(identifier));
return (state.AggregatedPropertyNames != null && state.AggregatedPropertyNames.Contains(identifier));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public ApplyClause BindApply(IEnumerable<QueryToken> 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:
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ internal string GetContextUri()
return CreatePropertiesUriSegment(lastGroupByPropertyNodes, lastAggregateStatements);
}

internal List<string> GetLastAggregatedPropertyNames()
{
if (lastAggregateStatements != null)
{
return lastAggregateStatements.Select(statement => statement.AsAlias).ToList();
}

return null;
}

private string CreatePropertiesUriSegment(
IEnumerable<GroupByPropertyNode> groupByPropertyNodes,
IEnumerable<AggregateStatement> aggregateStatements)
Expand Down
14 changes: 12 additions & 2 deletions src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public string ParseDeltaToken()
/// <param name="elementType">Type that the filter clause refers to.</param>
/// <param name="navigationSource">Navigation source that the elements being filtered are from.</param>
/// <returns>A <see cref="FilterClause"/> representing the metadata bound filter expression.</returns>
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");
Expand All @@ -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);
Expand Down Expand Up @@ -381,7 +386,7 @@ private static SelectExpandClause ParseSelectAndExpandImplementation(string sele
/// <param name="elementType">Type that the orderby clause refers to.</param>
/// <param name="navigationSource">NavigationSource that the elements are from.</param>
/// <returns>An <see cref="OrderByClause"/> representing the metadata bound orderby expression.</returns>
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");
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,77 @@ public void ActionsThrowOnClosedTypeInFilter()
parseWithAction.ShouldThrow<ODataException>().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<string, string>()
{
{"$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<string, string>()
{
{"$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<ConvertNode>().Source.ShouldBeSingleValueOpenPropertyAccessQueryNode("Total");
rightBinaryOperatorNode.Left.As<ConvertNode>().Source.ShouldBeSingleValueOpenPropertyAccessQueryNode("Max");
}

[Fact]
public void AggregatedPropertyTreatedAsOpenPropertyInOrderBy()
{
var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel,
HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet(),
new Dictionary<string, string>()
{
{"$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<string, string>()
{
{"$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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> { "Color" };
state.AggregatedPropertyNames = new List<string> { "Color" };
var metadataBinder = new MetadataBinder(state);
var endPathBinder = new EndPathBinder(metadataBinder.Bind, state);
var propertyNode = endPathBinder.GeneratePropertyAccessQueryForOpenType(
Expand Down

0 comments on commit e1f4ff0

Please sign in to comment.