diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java index e297a222e..994759651 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java @@ -28,7 +28,7 @@ import org.apache.olingo.server.api.uri.queryoption.expression.Expression; * Represents an aggregate expression. * @see Aggregate */ -public interface AggregateExpression { +public interface AggregateExpression extends Expression { /** Standard aggregation method. */ public enum StandardMethod { SUM, MIN, MAX, AVERAGE, COUNT_DISTINCT } diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java index f67a4958d..0bbef265d 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.olingo.commons.api.edm.EdmEnumType; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression; /** * Generic interface to define expression visitors with arbitrary return types. @@ -37,7 +38,7 @@ public interface ExpressionVisitor { * @param left Application return value of left sub tree * @param right Application return value of right sub tree * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitBinaryOperator(BinaryOperatorKind operator, T left, T right) @@ -48,7 +49,7 @@ public interface ExpressionVisitor { * @param operator Operator kind * @param operand return value of sub tree * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitUnaryOperator(UnaryOperatorKind operator, T operand) @@ -71,7 +72,7 @@ public interface ExpressionVisitor { * @param lambdaVariable Variable name used lambda variable * @param expression Lambda expression * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitLambdaExpression(String lambdaFunction, String lambdaVariable, Expression expression) @@ -81,7 +82,7 @@ public interface ExpressionVisitor { * Called for each traversed {@link Literal} expression * @param literal Literal * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitLiteral(Literal literal) throws ExpressionVisitException, ODataApplicationException; @@ -91,7 +92,7 @@ public interface ExpressionVisitor { * @param member UriInfoResource object describing the whole path used to access an data value * (this includes for example the usage of $root and $it inside the URI) * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitMember(Member member) throws ExpressionVisitException, ODataApplicationException; @@ -100,7 +101,7 @@ public interface ExpressionVisitor { * Called for each traversed {@link Alias} expression * @param aliasName Name of the alias * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitAlias(String aliasName) throws ExpressionVisitException, ODataApplicationException; @@ -109,7 +110,7 @@ public interface ExpressionVisitor { * Called for each traversed {@link TypeLiteral} expression * @param type EdmType * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitTypeLiteral(EdmType type) throws ExpressionVisitException, ODataApplicationException; @@ -118,7 +119,7 @@ public interface ExpressionVisitor { * Called for each traversed {@link LambdaRef} * @param variableName Name of the used lambda variable * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitLambdaReference(String variableName) throws ExpressionVisitException, ODataApplicationException; @@ -128,7 +129,7 @@ public interface ExpressionVisitor { * @param type Type used in the URI before the enumeration values * @param enumValues List of enumeration values * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitEnum(EdmEnumType type, List enumValues) throws ExpressionVisitException, ODataApplicationException; @@ -139,10 +140,22 @@ public interface ExpressionVisitor { * @param left Application return value of left sub tree * @param right Application return value of right sub tree * @return Application return value of type T - * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ExpressionVisitException Thrown if an exception while traversing occurred * @throws ODataApplicationException Thrown by the application */ T visitBinaryOperator(BinaryOperatorKind operator, T left, List right) throws ExpressionVisitException, ODataApplicationException; + /** + * Called for each traversed {@link AggregateExpression} + * @param aggregateExpr the aggregate expression + * @return Application return value of type T + * @throws ExpressionVisitException Thrown if an exception while traversing occurred + * @throws ODataApplicationException Thrown by the application + */ + default T visitComputeAggregate(AggregateExpression aggregateExpr) + throws ExpressionVisitException, ODataApplicationException { + throw new UnsupportedOperationException(); + } + } diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java index 527fddc8a..0b88da97c 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java @@ -23,6 +23,7 @@ package org.apache.olingo.server.api.uri.queryoption.expression; * For the semantic of these methods please see the ODATA specification for URL conventions */ public enum MethodKind { + COMPUTE_AGGREGATE("aggregate"), CONTAINS("contains"), STARTSWITH("startswith"), ENDSWITH("endswith"), diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java index 7cc0f3b12..6cfe85dba 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java @@ -222,79 +222,84 @@ public class ApplyParser { throws UriParserException, UriValidationException { AggregateImpl aggregate = new AggregateImpl(); do { - aggregate.addExpression(parseAggregateExpr(referencedType)); + aggregate.addExpression(parseAggregateExpr(referencedType, Requirement.REQUIRED)); } while (tokenizer.next(TokenKind.COMMA)); ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); return aggregate; } + + public AggregateExpression parseAggregateMethodCallExpr(UriTokenizer tokenizer, EdmStructuredType referringType) + throws UriParserException, UriValidationException { + this.tokenizer = tokenizer; - private AggregateExpression parseAggregateExpr(EdmStructuredType referencedType) + return parseAggregateExpr(referringType, Requirement.FORBIDDEN); + } + + private AggregateExpression parseAggregateExpr(EdmStructuredType referencedType, Requirement aliasRequired) throws UriParserException, UriValidationException { - AggregateExpressionImpl aggregateExpression = new AggregateExpressionImpl(); - tokenizer.saveState(); + AggregateExpressionImpl aggregateExpression = new AggregateExpressionImpl(); + tokenizer.saveState(); - // First try is checking for a (potentially empty) path prefix and the things that could follow it. - UriInfoImpl uriInfo = new UriInfoImpl(); - final String identifierLeft = parsePathPrefix(uriInfo, referencedType); - if (identifierLeft != null) { - customAggregate(referencedType, aggregateExpression, uriInfo); - } else if (tokenizer.next(TokenKind.OPEN)) { - final UriResource lastResourcePart = uriInfo.getLastResourcePart(); - if (lastResourcePart == null) { - throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.", - UriParserSyntaxException.MessageKeys.SYNTAX); - } - aggregateExpression.setPath(uriInfo); - DynamicStructuredType inlineType = new DynamicStructuredType((EdmStructuredType) - ParserHelper.getTypeInformation((UriResourcePartTyped) lastResourcePart)); - aggregateExpression.setInlineAggregateExpression(parseAggregateExpr(inlineType)); - ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); - } else if (tokenizer.next(TokenKind.COUNT)) { - uriInfo.addResourcePart(new UriResourceCountImpl()); - aggregateExpression.setPath(uriInfo); - final String alias = parseAsAlias(referencedType, true); - aggregateExpression.setAlias(alias); - ((DynamicStructuredType) referencedType).addProperty( - createDynamicProperty(alias, - // The OData standard mandates Edm.Decimal (with no decimals), although counts are always integer. - odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal))); - } else { - // No legitimate continuation of a path prefix has been found. + // First try is checking for a (potentially empty) path prefix and the things that could follow it. + UriInfoImpl uriInfo = new UriInfoImpl(); + final String identifierLeft = parsePathPrefix(uriInfo, referencedType); + if (identifierLeft != null) { + customAggregate(referencedType, aggregateExpression, uriInfo); + } else if (tokenizer.next(TokenKind.OPEN)) { + final UriResource lastResourcePart = uriInfo.getLastResourcePart(); + if (lastResourcePart == null) { + throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + aggregateExpression.setPath(uriInfo); + DynamicStructuredType inlineType = new DynamicStructuredType((EdmStructuredType) + ParserHelper.getTypeInformation((UriResourcePartTyped) lastResourcePart)); + aggregateExpression.setInlineAggregateExpression(parseAggregateExpr(inlineType, aliasRequired)); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + } else if (tokenizer.next(TokenKind.COUNT)) { + uriInfo.addResourcePart(new UriResourceCountImpl()); + aggregateExpression.setPath(uriInfo); + final String alias = parseAsAlias(referencedType, aliasRequired); + if (alias != null) { + aggregateExpression.setAlias(alias); + ((DynamicStructuredType) referencedType).addProperty( + createDynamicProperty(alias, + // The OData standard mandates Edm.Decimal (with no decimals), + // although counts are always integer. + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal))); + } + } else { + // No legitimate continuation of a path prefix has been found. - // Second try is checking for a common expression. - tokenizer.returnToSavedState(); - final Expression expression = new ExpressionParser(edm, odata) - .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases); - aggregateExpression.setExpression(expression); - parseAggregateWith(aggregateExpression); - if (aggregateExpression.getStandardMethod() == null && aggregateExpression.getCustomMethod() == null) { - if (tokenizer.next(TokenKind.AsOperator)) { - throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.", - UriParserSyntaxException.MessageKeys.SYNTAX); - } - customAggregateNamedAsProperty(referencedType, aggregateExpression, uriInfo); - - return aggregateExpression; - } - final String alias = parseAsAlias(referencedType, true); - aggregateExpression.setAlias(alias); - DynamicProperty dynamicProperty = createDynamicProperty(alias, - // Determine the type for standard methods; there is no way to do this for custom methods. - getTypeForAggregateMethod(aggregateExpression.getStandardMethod(), - ExpressionParser.getType(expression))); - if (aggregateExpression.getStandardMethod() == StandardMethod.SUM - || aggregateExpression.getStandardMethod() == StandardMethod.AVERAGE) { - //by default a property with no precision/scale defaults to a 0 scale - //this does not work for sum/average in general - dynamicProperty.setScale(Integer.MAX_VALUE); - } - ((DynamicStructuredType) referencedType).addProperty( - dynamicProperty); - parseAggregateFrom(aggregateExpression, referencedType); - } + // Second try is checking for a common expression. + tokenizer.returnToSavedState(); + final Expression expression = new ExpressionParser(edm, odata) + .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases); + aggregateExpression.setExpression(expression); + parseAggregateWith(aggregateExpression); + if (aggregateExpression.getStandardMethod() == null && aggregateExpression.getCustomMethod() == null) { + if (tokenizer.next(TokenKind.AsOperator)) { + throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + customAggregateNamedAsProperty(referencedType, aggregateExpression, uriInfo); + + return aggregateExpression; + } + final String alias = parseAsAlias(referencedType, aliasRequired); + if(alias != null) { + aggregateExpression.setAlias(alias); + ((DynamicStructuredType) referencedType).addProperty( + createDynamicProperty(alias, + // Determine the type for standard methods; there is no way to do this for custom methods. + getTypeForAggregateMethod(aggregateExpression.getStandardMethod(), + ExpressionParser.getType(expression)))); + } + parseAggregateFrom(aggregateExpression, referencedType); + } - return aggregateExpression; - } + return aggregateExpression; + } private void customAggregate(EdmStructuredType referencedType, AggregateExpressionImpl aggregateExpression, UriInfoImpl uriInfo) throws UriParserException { @@ -306,7 +311,7 @@ public class ApplyParser { // allowed and have no type. uriInfo.addResourcePart(new UriResourcePrimitivePropertyImpl(createDynamicProperty(customAggregate, null))); aggregateExpression.setPath(uriInfo); - final String alias = parseAsAlias(referencedType, false); + final String alias = parseAsAlias(referencedType, Requirement.OPTIONAL); if (alias != null) { aggregateExpression.setAlias(alias); ((DynamicStructuredType) referencedType).addProperty(createDynamicProperty(alias, null)); @@ -361,22 +366,31 @@ public class ApplyParser { return null; } } + + enum Requirement { + REQUIRED, OPTIONAL, FORBIDDEN + } - private String parseAsAlias(final EdmStructuredType referencedType, final boolean isRequired) + private String parseAsAlias(final EdmStructuredType referencedType, final Requirement requirement) throws UriParserException { - if (tokenizer.next(TokenKind.AsOperator)) { - ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); - final String name = tokenizer.getText(); - if (referencedType.getProperty(name) != null) { - throw new UriParserSemanticException("Alias '" + name + "' is already a property.", - UriParserSemanticException.MessageKeys.IS_PROPERTY, name); - } - return name; - } else if (isRequired) { - throw new UriParserSyntaxException("Expected asAlias not found.", UriParserSyntaxException.MessageKeys.SYNTAX); - } - return null; - } + if (tokenizer.next(TokenKind.AsOperator)) { + if(requirement == Requirement.FORBIDDEN) { + throw new UriParserSyntaxException("Unexpected as alias found.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); + final String name = tokenizer.getText(); + if (referencedType.getProperty(name) != null) { + throw new UriParserSemanticException("Alias '" + name + "' is already a property.", + UriParserSemanticException.MessageKeys.IS_PROPERTY, name); + } + return name; + } else if (requirement == Requirement.REQUIRED) { + throw new UriParserSyntaxException("Expected as alias not found.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + return null; + } private void parseAggregateFrom(AggregateExpressionImpl aggregateExpression, final EdmStructuredType referencedType) throws UriParserException { @@ -403,7 +417,7 @@ public class ApplyParser { throw new UriParserSemanticException("Compute expressions must return primitive values.", UriParserSemanticException.MessageKeys.ONLY_FOR_PRIMITIVE_TYPES, "compute"); } - final String alias = parseAsAlias(referencedType, true); + final String alias = parseAsAlias(referencedType, Requirement.REQUIRED); ((DynamicStructuredType) referencedType).addProperty(createDynamicProperty(alias, expressionType)); compute.addExpression(new ComputeExpressionImpl() .setExpression(expression) diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java index 8f69f818d..40a97f5aa 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java @@ -54,6 +54,7 @@ import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; +import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression; import org.apache.olingo.server.api.uri.queryoption.expression.Alias; import org.apache.olingo.server.api.uri.queryoption.expression.Binary; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; @@ -123,6 +124,7 @@ public class ExpressionParser { private static final Map tokenToMethod; static { Map temp = new EnumMap<>(TokenKind.class); + temp.put(TokenKind.AggregateTrafo, MethodKind.COMPUTE_AGGREGATE); temp.put(TokenKind.CeilingMethod, MethodKind.CEILING); temp.put(TokenKind.ConcatMethod, MethodKind.CONCAT); temp.put(TokenKind.ContainsMethod, MethodKind.CONTAINS); @@ -660,8 +662,16 @@ public class ExpressionParser { case CAST: case ISOF: break; + + case COMPUTE_AGGREGATE: + ApplyParser ap = new ApplyParser(edm, odata); + AggregateExpression aggrExpr = ap.parseAggregateMethodCallExpr(tokenizer, + (EdmStructuredType) referringType); + parameters.add(aggrExpr); + } ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + return parameters; } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java index ed14a339c..f7f6fd9fd 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java @@ -23,10 +23,13 @@ import java.util.Collections; import java.util.List; import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; +import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; +import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor; /** * Represents an aggregate expression. @@ -110,4 +113,10 @@ public class AggregateExpressionImpl implements AggregateExpression { this.alias = alias; return this; } + + @Override + public T accept(ExpressionVisitor visitor) throws ExpressionVisitException, ODataApplicationException { + // TODO Auto-generated method stub + return null; + } } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java index b6e7f2c05..aace98c42 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java @@ -112,8 +112,11 @@ public class MethodImpl implements Method { case ISOF: kind = EdmPrimitiveTypeKind.Boolean; break; + case COMPUTE_AGGREGATE: + kind = null; } - return new ODataImpl().createPrimitiveTypeInstance(kind); + + return kind == null ? null : new ODataImpl().createPrimitiveTypeInstance(kind); } @Override diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java index 25babe295..8307c4729 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java @@ -127,22 +127,53 @@ public class ApplyParserTest { @Test public void customAggregate() throws Exception { - parse("ESTwoKeyNav", "aggregate(customAggregate)") - .is(Aggregate.class) - .goAggregate(0) - .noExpression() - .noInlineAggregateExpression() - .goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty); + parse("ESTwoKeyNav", "aggregate(customAggregate)").is(Aggregate.class).goAggregate(0).noExpression() + .noInlineAggregateExpression().goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty); } @Test public void customAggregateNamedAsProperty() throws Exception { - parse("ESTwoKeyNav", "aggregate(PropertyInt16)") - .is(Aggregate.class) - .goAggregate(0) - .noExpression() - .noInlineAggregateExpression() - .goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + parse("ESTwoKeyNav", "aggregate(PropertyInt16)").is(Aggregate.class).goAggregate(0).noExpression() + .noInlineAggregateExpression().goPath().first() + .isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + } + + @Test + public void filterByCustomAggregate() throws Exception { + ApplyValidator validator = parse("ESTwoKeyNav", "filter(aggregate(customAggregate) gt 5)"); + FilterValidator filter = validator.is(Filter.class).goFilter().isBinary(BinaryOperatorKind.GT); + AggregateValidator aggregate = aggregate(filter.root().left()); + aggregate.goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty); + filter.root().right().isLiteral("5"); + } + + @Test + public void filterByCountAggregate() throws Exception { + ApplyValidator validator = parse("ESTwoKeyNav", "filter(aggregate($count) ge 9)"); + FilterValidator filter = validator.is(Filter.class).goFilter().isBinary(BinaryOperatorKind.GE); + AggregateValidator aggregate = aggregate(filter.root().left()); + aggregate.goPath().first().isCount(); + filter.root().right().isLiteral("9"); + } + + @Test + public void filterByTransformationAggregate() throws Exception { + ApplyValidator validator = parse("ESTwoKeyNav", "filter(aggregate(PropertyInt16 with min) ne 3)"); + FilterValidator filter = validator.is(Filter.class).goFilter().isBinary(BinaryOperatorKind.NE); + AggregateValidator aggregate = aggregate(filter.root().left()); + aggregate.isStandardMethod(StandardMethod.MIN) + .goExpression().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + filter.root().right().isLiteral("3"); + } + + @Test + public void filterByCustomAggregateNamedAsProperty() throws Exception { + ApplyValidator validator = parse("ESTwoKeyNav", "filter(aggregate(PropertyString) eq 'Evgeny')"); + FilterValidator filter = validator.is(Filter.class).goFilter().isBinary(BinaryOperatorKind.EQ); + AggregateValidator aggregate = aggregate(filter.root().left()); + aggregate.goPath().first() + .isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false); + filter.root().right().isLiteral("'Evgeny'"); } @Test @@ -660,6 +691,14 @@ public class ApplyParserTest { return previous; } } + + private AggregateValidator aggregate(FilterValidator filter) { + filter.isMethod(MethodKind.COMPUTE_AGGREGATE, 1); + filter.goParameter(0); + + return new AggregateValidator((AggregateExpression) filter.getExpression(), filter); + } + private final class AggregateValidator implements TestValidator { @@ -670,7 +709,7 @@ public class ApplyParserTest { this.aggregateExpression = aggregateExpression; this.previous = previous; } - + public AggregateValidator isStandardMethod(final AggregateExpression.StandardMethod method) { assertNotNull(aggregateExpression); assertEquals(method, aggregateExpression.getStandardMethod()); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java index c68ecde94..c4cacf747 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java @@ -91,6 +91,10 @@ public class FilterValidator implements TestValidator { rootExpression = curExpression = expression; return this; } + + public Expression getExpression() { + return curExpression; + } // --- Execution --- @@ -375,7 +379,7 @@ public class FilterValidator implements TestValidator { assertTrue("Current expression not a member", curExpression instanceof Member); return this; } - + public FilterValidator isMemberStartType(final FullQualifiedName fullName) { isMember(); Member member = (Member) curExpression;