[OLINGO-1028] stricter multiplicity tests in expression parser + clean-up Signed-off-by: Christian Amend <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/72fcaa1a Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/72fcaa1a Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/72fcaa1a Branch: refs/heads/master Commit: 72fcaa1a54a3607ee2b94f66414677ab6f9c8e92 Parents: b9a71ff Author: Klaus Straubinger <[email protected]> Authored: Thu Sep 29 16:25:33 2016 +0200 Committer: Christian Amend <[email protected]> Committed: Thu Sep 29 16:31:24 2016 +0200 ---------------------------------------------------------------------- .../core/uri/parser/ExpressionParser.java | 59 +- .../uri/parser/UriParserSemanticException.java | 3 +- .../uri/queryoption/expression/MemberImpl.java | 14 +- .../server-core-exceptions-i18n.properties | 1 + .../core/uri/parser/ExpandParserTest.java | 561 ++ .../core/uri/parser/ExpressionParserTest.java | 2389 +++++++ .../core/uri/parser/ResourcePathParserTest.java | 2100 ++++++ .../core/uri/parser/SearchParserTest.java | 187 + .../core/uri/parser/SelectParserTest.java | 177 + .../core/uri/parser/TestFullResourcePath.java | 6003 ------------------ .../core/uri/parser/TestUriParserImpl.java | 1047 --- .../server/core/uri/parser/UriParserTest.java | 843 +++ .../core/uri/testutil/ResourceValidator.java | 221 +- 13 files changed, 6388 insertions(+), 7217 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/72fcaa1a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java ---------------------------------------------------------------------- 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 23fd8a4..7a34095 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 @@ -180,15 +180,19 @@ public class ExpressionParser { this.crossjoinEntitySetNames = crossjoinEntitySetNames; this.aliases = aliases; - return parseExpression(); + final Expression expression = parseExpression(); + checkNoCollection(expression); + return expression; } private Expression parseExpression() throws UriParserException, UriValidationException { Expression left = parseAnd(); while (tokenizer.next(TokenKind.OrOperator)) { - final Expression right = parseAnd(); checkType(left, EdmPrimitiveTypeKind.Boolean); + checkNoCollection(left); + final Expression right = parseAnd(); checkType(right, EdmPrimitiveTypeKind.Boolean); + checkNoCollection(right); left = new BinaryImpl(left, BinaryOperatorKind.OR, right, odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); } @@ -198,9 +202,11 @@ public class ExpressionParser { private Expression parseAnd() throws UriParserException, UriValidationException { Expression left = parseExprEquality(); while (tokenizer.next(TokenKind.AndOperator)) { - final Expression right = parseExprEquality(); checkType(left, EdmPrimitiveTypeKind.Boolean); + checkNoCollection(left); + final Expression right = parseExprEquality(); checkType(right, EdmPrimitiveTypeKind.Boolean); + checkNoCollection(right); left = new BinaryImpl(left, BinaryOperatorKind.AND, right, odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); } @@ -285,8 +291,8 @@ public class ExpressionParser { TokenKind.MulOperator, TokenKind.DivOperator, TokenKind.ModOperator); // Null for everything other than MUL or DIV or MOD while (operatorTokenKind != null) { - final Expression right = parseExprUnary(); checkNumericType(left); + final Expression right = parseExprUnary(); checkNumericType(right); left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double)); @@ -306,6 +312,7 @@ public class ExpressionParser { } else if (tokenizer.next(TokenKind.NotOperator)) { final Expression expression = parseExprPrimary(); checkType(expression, EdmPrimitiveTypeKind.Boolean); + checkNoCollection(expression); return new UnaryImpl(UnaryOperatorKind.NOT, expression, getType(expression)); } else if (tokenizer.next(TokenKind.CastMethod)) { return parseIsOfOrCastMethod(MethodKind.CAST); @@ -443,6 +450,7 @@ public class ExpressionParser { case TRIM: final Expression stringParameter = parseExpression(); checkType(stringParameter, EdmPrimitiveTypeKind.String); + checkNoCollection(stringParameter); parameters.add(stringParameter); break; case YEAR: @@ -450,6 +458,7 @@ public class ExpressionParser { case DAY: final Expression dateParameter = parseExpression(); checkType(dateParameter, EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.DateTimeOffset); + checkNoCollection(dateParameter); parameters.add(dateParameter); break; case HOUR: @@ -458,6 +467,7 @@ public class ExpressionParser { case FRACTIONALSECONDS: final Expression timeParameter = parseExpression(); checkType(timeParameter, EdmPrimitiveTypeKind.TimeOfDay, EdmPrimitiveTypeKind.DateTimeOffset); + checkNoCollection(timeParameter); parameters.add(timeParameter); break; case DATE: @@ -465,11 +475,13 @@ public class ExpressionParser { case TOTALOFFSETMINUTES: final Expression dateTimeParameter = parseExpression(); checkType(dateTimeParameter, EdmPrimitiveTypeKind.DateTimeOffset); + checkNoCollection(dateTimeParameter); parameters.add(dateTimeParameter); break; case TOTALSECONDS: final Expression durationParameter = parseExpression(); checkType(durationParameter, EdmPrimitiveTypeKind.Duration); + checkNoCollection(durationParameter); parameters.add(durationParameter); break; case ROUND: @@ -478,12 +490,14 @@ public class ExpressionParser { final Expression decimalParameter = parseExpression(); checkType(decimalParameter, EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double); + checkNoCollection(decimalParameter); parameters.add(decimalParameter); break; case GEOLENGTH: final Expression geoParameter = parseExpression(); checkType(geoParameter, EdmPrimitiveTypeKind.GeographyLineString, EdmPrimitiveTypeKind.GeometryLineString); + checkNoCollection(geoParameter); parameters.add(geoParameter); break; @@ -495,30 +509,36 @@ public class ExpressionParser { case CONCAT: final Expression stringParameter1 = parseExpression(); checkType(stringParameter1, EdmPrimitiveTypeKind.String); + checkNoCollection(stringParameter1); parameters.add(stringParameter1); ParserHelper.requireNext(tokenizer, TokenKind.COMMA); final Expression stringParameter2 = parseExpression(); checkType(stringParameter2, EdmPrimitiveTypeKind.String); + checkNoCollection(stringParameter2); parameters.add(stringParameter2); break; case GEODISTANCE: final Expression geoParameter1 = parseExpression(); checkType(geoParameter1, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint); + checkNoCollection(geoParameter1); parameters.add(geoParameter1); ParserHelper.requireNext(tokenizer, TokenKind.COMMA); final Expression geoParameter2 = parseExpression(); checkType(geoParameter2, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint); + checkNoCollection(geoParameter2); parameters.add(geoParameter2); break; case GEOINTERSECTS: final Expression geoPointParameter = parseExpression(); checkType(geoPointParameter, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint); + checkNoCollection(geoPointParameter); parameters.add(geoPointParameter); ParserHelper.requireNext(tokenizer, TokenKind.COMMA); final Expression geoPolygonParameter = parseExpression(); checkType(geoPolygonParameter, EdmPrimitiveTypeKind.GeographyPolygon, EdmPrimitiveTypeKind.GeometryPolygon); + checkNoCollection(geoPolygonParameter); parameters.add(geoPolygonParameter); break; @@ -526,6 +546,7 @@ public class ExpressionParser { case SUBSTRING: final Expression parameterFirst = parseExpression(); checkType(parameterFirst, EdmPrimitiveTypeKind.String); + checkNoCollection(parameterFirst); parameters.add(parameterFirst); ParserHelper.requireNext(tokenizer, TokenKind.COMMA); final Expression parameterSecond = parseExpression(); @@ -905,8 +926,9 @@ public class ExpressionParser { } else if (tokenizer.next(TokenKind.ALL)) { uriInfo.addResourcePart(parseLambdaRest(TokenKind.ALL, lastResource)); } else if (tokenizer.next(TokenKind.QualifiedName)) { - final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText()); - parseBoundFunction(fullQualifiedName, uriInfo, lastResource); + parseBoundFunction(new FullQualifiedName(tokenizer.getText()), uriInfo, lastResource); + } else { + throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX); } } @@ -1071,13 +1093,22 @@ public class ExpressionParser { } } + private void checkNoCollection(final Expression expression) throws UriParserException { + if (expression instanceof Member && ((Member) expression).isCollection()) { + throw new UriParserSemanticException("Collection not allowed.", + UriParserSemanticException.MessageKeys.COLLECTION_NOT_ALLOWED); + } + } + protected void checkIntegerType(final Expression expression) throws UriParserException { + checkNoCollection(expression); checkType(expression, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte); } protected void checkNumericType(final Expression expression) throws UriParserException { + checkNoCollection(expression); checkType(expression, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, @@ -1085,6 +1116,9 @@ public class ExpressionParser { } private void checkEqualityTypes(final Expression left, final Expression right) throws UriParserException { + checkNoCollection(left); + checkNoCollection(right); + final EdmType leftType = getType(left); final EdmType rightType = getType(right); if (leftType == null || rightType == null || leftType.equals(rightType)) { @@ -1141,11 +1175,10 @@ public class ExpressionParser { } private void checkRelationTypes(final Expression left, final Expression right) throws UriParserException { + checkNoCollection(left); + checkNoCollection(right); final EdmType leftType = getType(left); final EdmType rightType = getType(right); - if (leftType == null || rightType == null) { - return; - } checkType(left, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, @@ -1160,6 +1193,9 @@ public class ExpressionParser { EdmPrimitiveTypeKind.Boolean, EdmPrimitiveTypeKind.Guid, EdmPrimitiveTypeKind.String, EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.TimeOfDay, EdmPrimitiveTypeKind.DateTimeOffset, EdmPrimitiveTypeKind.Duration); + if (leftType == null || rightType == null) { + return; + } if (!(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType) || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) { throw new UriParserSemanticException("Incompatible types.", @@ -1171,11 +1207,10 @@ public class ExpressionParser { private EdmType getAddSubTypeAndCheckLeftAndRight(final Expression left, final Expression right, final boolean isSub) throws UriParserException { + checkNoCollection(left); + checkNoCollection(right); final EdmType leftType = getType(left); final EdmType rightType = getType(right); - if (leftType == null || rightType == null) { - return null; - } if (isType(leftType, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/72fcaa1a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java index f2c4634..8260af7 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java @@ -74,7 +74,8 @@ public class UriParserSemanticException extends UriParserException { /** parameter: expression */ ONLY_FOR_PRIMITIVE_TYPES, /** parameter: function name */ - FUNCTION_MUST_USE_COLLECTIONS; + FUNCTION_MUST_USE_COLLECTIONS, + COLLECTION_NOT_ALLOWED; @Override public String getKey() { http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/72fcaa1a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MemberImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MemberImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MemberImpl.java index 3306565..4a67231 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MemberImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MemberImpl.java @@ -21,6 +21,8 @@ package org.apache.olingo.server.core.uri.queryoption.expression; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor; import org.apache.olingo.server.api.uri.queryoption.expression.Member; @@ -85,14 +87,10 @@ public class MemberImpl implements Member { @Override public boolean isCollection() { UriInfoImpl uriInfo = (UriInfoImpl) path; - UriResourceImpl lastResourcePart = (UriResourceImpl) uriInfo.getLastResourcePart(); - if (lastResourcePart instanceof UriResourceTypedImpl) { - UriResourceTypedImpl lastTyped = (UriResourceTypedImpl) lastResourcePart; - return lastTyped.isCollection(); - } else if (lastResourcePart instanceof UriResourceActionImpl) { - return ((UriResourceActionImpl) lastResourcePart).isCollection(); - } - return false; + UriResource lastResourcePart = uriInfo.getLastResourcePart(); + return lastResourcePart instanceof UriResourcePartTyped ? + ((UriResourcePartTyped) lastResourcePart).isCollection() : + false; } @Override http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/72fcaa1a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties index 831fe37..5768565 100644 --- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties +++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties @@ -76,6 +76,7 @@ UriParserSemanticException.NOT_A_MEDIA_RESOURCE=The resource '%1$s' is not a med UriParserSemanticException.IS_PROPERTY=The identifier '%1$s' is already used as a property. UriParserSemanticException.ONLY_FOR_PRIMITIVE_TYPES='%1$s' is only allowed for primitive-type expressions. UriParserSemanticException.FUNCTION_MUST_USE_COLLECTIONS=Only bound functions with collections of structural types as binding parameter and as return type are allowed; '%1$s' is not such a function. +UriParserSemanticException.COLLECTION_NOT_ALLOWED=A collection expression is not allowed. UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported. UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported. http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/72fcaa1a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpandParserTest.java ---------------------------------------------------------------------- diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpandParserTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpandParserTest.java new file mode 100644 index 0000000..ebc4cc6 --- /dev/null +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpandParserTest.java @@ -0,0 +1,561 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.core.uri.parser; + +import java.util.Collections; + +import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edmx.EdmxReference; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.uri.UriInfoKind; +import org.apache.olingo.server.core.uri.parser.UriParserSemanticException.MessageKeys; +import org.apache.olingo.server.core.uri.testutil.TestUriValidator; +import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; +import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; +import org.apache.olingo.server.tecsvc.provider.EntityTypeProvider; +import org.apache.olingo.server.tecsvc.provider.PropertyProvider; +import org.junit.Test; + +/** Tests of the parts of the URI parser that parse the sytem query option $expand. */ +public class ExpandParserTest { + + private static final Edm edm = OData.newInstance().createServiceMetadata( + new EdmTechProvider(), Collections.<EdmxReference> emptyList()).getEdm(); + + private final TestUriValidator testUri = new TestUriValidator().setEdm(edm); + + @Test + public void expandStar() throws Exception { + testUri.run("ESKeyNav(1)", "$expand=*") + .isKind(UriInfoKind.resource).goExpand() + .first() + .isSegmentStar(); + + testUri.run("ESKeyNav(1)", "$expand=*/$ref") + .isKind(UriInfoKind.resource).goExpand() + .first() + .isSegmentStar() + .isSegmentRef(); + + testUri.run("ESKeyNav(1)", "$expand=*/$ref,NavPropertyETKeyNavMany") + .isKind(UriInfoKind.resource).goExpand() + .first() + .isSegmentStar().isSegmentRef() + .next() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true); + + testUri.run("ESKeyNav(1)", "$expand=*($levels=3)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .isSegmentStar() + .isLevelText("3"); + + testUri.run("ESKeyNav(1)", "$expand=*($levels=max)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .isSegmentStar() + .isLevelText("max"); + } + + @Test + public void expandNavigationRef() throws Exception { + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef(); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavOne/$ref") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav, false) + .n().isRef(); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($filter=PropertyInt16 eq 1)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator().goFilter().is("<<PropertyInt16> eq <1>>"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($orderby=PropertyInt16)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .isSortOrder(0, false) + .goOrder(0).goPath().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($skip=1)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .isSkipText("1"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($top=2)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .isTopText("2"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($count=true)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .isInlineCountText("true"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($skip=1;$top=3)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .isSkipText("1") + .isTopText("3"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref($skip=1%3b$top=3)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .isSkipText("1") + .isTopText("3"); + } + + @Test + public void expandNavigationCount() throws Exception { + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$count") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isCount(); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavOne/$count") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav, false) + .n().isCount(); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$count($filter=PropertyInt16 gt 1)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .n().isCount() + .goUpExpandValidator() + .goFilter().is("<<PropertyInt16> gt <1>>"); + } + + @Test + public void expandNavigationOptions() throws Exception { + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($filter=PropertyInt16 eq 1)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator().goFilter().is("<<PropertyInt16> eq <1>>"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($orderby=PropertyInt16)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isSortOrder(0, false) + .goOrder(0).goPath().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($skip=1)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isSkipText("1"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($top=2)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isTopText("2"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($count=true)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isInlineCountText("true"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($select=PropertyString)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .goSelectItem(0).isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($expand=NavPropertyETTwoKeyNavOne)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .goExpand() + .goPath().first() + .isNavProperty("NavPropertyETTwoKeyNavOne", EntityTypeProvider.nameETTwoKeyNav, false); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($expand=NavPropertyETKeyNavMany)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .goExpand() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavOne($levels=5)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav, false) + .goUpExpandValidator() + .isLevelText("5"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($select=PropertyString)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .goSelectItem(0).isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavOne($levels=max)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav, false) + .goUpExpandValidator() + .isLevelText("max"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($skip=1;$top=2)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isSkipText("1") + .isTopText("2"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($skip=1%3b$top=2)") + .isKind(UriInfoKind.resource).goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isSkipText("1") + .isTopText("2"); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany($search=Country AND Western)") + .isKind(UriInfoKind.resource).goExpand() + .first().goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .goUpExpandValidator() + .isSearchSerialized("{'Country' AND 'Western'}"); + + testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='Hugo')", "$expand=NavPropertyETKeyNavMany") + .isKind(UriInfoKind.resource).goPath() + .first() + .isKeyPredicate(0, "PropertyInt16", "1") + .isKeyPredicate(1, "PropertyString", "'Hugo'") + .goUpUriValidator().goExpand() + .first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav, true); + } + + @Test + public void expandTypeCasts() throws Exception { + testUri.run("ESTwoKeyNav", "$expand=olingo.odata.test1.ETBaseTwoKeyNav/NavPropertyETKeyNavMany") + .isKind(UriInfoKind.resource) + .goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true); + + testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='Hugo')", + "$expand=olingo.odata.test1.ETBaseTwoKeyNav/NavPropertyETKeyNavMany") + .isKind(UriInfoKind.resource).goPath().first() + .isKeyPredicate(0, "PropertyInt16", "1") + .isKeyPredicate(1, "PropertyString", "'Hugo'") + .goUpUriValidator().goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isType(EntityTypeProvider.nameETKeyNav) + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true); + + testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='2')", + "$expand=olingo.odata.test1.ETBaseTwoKeyNav/NavPropertyETTwoKeyNavMany") + .isKind(UriInfoKind.resource).goPath().first() + .isKeyPredicate(0, "PropertyInt16", "1") + .isKeyPredicate(1, "PropertyString", "'2'") + .goUpUriValidator().goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isType(EntityTypeProvider.nameETTwoKeyNav) + .isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true); + + testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='2')", + "$expand=olingo.odata.test1.ETBaseTwoKeyNav/NavPropertyETTwoKeyNavMany/olingo.odata.test1.ETTwoBaseTwoKeyNav") + .isKind(UriInfoKind.resource).goPath().first() + .isKeyPredicate(0, "PropertyInt16", "1") + .isKeyPredicate(1, "PropertyString", "'2'") + .goUpUriValidator().goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .isTypeFilterOnCollection(EntityTypeProvider.nameETTwoBaseTwoKeyNav); + + testUri.run("ESTwoKeyNav", "$expand=olingo.odata.test1.ETBaseTwoKeyNav/PropertyCompNav/NavPropertyETTwoKeyNavOne") + .isKind(UriInfoKind.resource) + .goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isComplexProperty("PropertyCompNav", ComplexTypeProvider.nameCTBasePrimCompNav, false) + .n() + .isNavProperty("NavPropertyETTwoKeyNavOne", EntityTypeProvider.nameETTwoKeyNav, false); + + testUri.run("ESTwoKeyNav", "$expand=olingo.odata.test1.ETBaseTwoKeyNav/PropertyCompNav/*") + .isKind(UriInfoKind.resource) + .goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .isSegmentStar() + .goPath().first().isComplexProperty("PropertyCompNav", ComplexTypeProvider.nameCTBasePrimCompNav, false); + + testUri.run("ESTwoKeyNav", "$expand=olingo.odata.test1.ETBaseTwoKeyNav/PropertyCompNav" + + "/olingo.odata.test1.CTTwoBasePrimCompNav/NavPropertyETTwoKeyNavOne") + .isKind(UriInfoKind.resource) + .goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isComplexProperty("PropertyCompNav", ComplexTypeProvider.nameCTBasePrimCompNav, false) + .isTypeFilter(ComplexTypeProvider.nameCTTwoBasePrimCompNav) + .n() + .isNavProperty("NavPropertyETTwoKeyNavOne", EntityTypeProvider.nameETTwoKeyNav, false); + + testUri.run("ESKeyNav", "$expand=NavPropertyETTwoKeyNavMany/Namespace1_Alias.ETBaseTwoKeyNav" + + "($expand=NavPropertyETBaseTwoKeyNavOne)") + .isKind(UriInfoKind.resource) + .goExpand().goPath().first() + .isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .isType(EntityTypeProvider.nameETTwoKeyNav, true) + .isTypeFilterOnCollection(EntityTypeProvider.nameETBaseTwoKeyNav) + .goUpExpandValidator() + // go to the expand options of the current expand + .goExpand() + .goPath().first() + .isNavProperty("NavPropertyETBaseTwoKeyNavOne", EntityTypeProvider.nameETBaseTwoKeyNav, false); + + testUri.run("ESKeyNav(1)", "$expand=NavPropertyETKeyNavMany/$ref,NavPropertyETTwoKeyNavMany($skip=2;$top=1)") + .isKind(UriInfoKind.resource) + .goExpand().first() + .goPath() + .first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .n().isRef() + .goUpExpandValidator() + .next() + .goPath() + .first().isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .goUpExpandValidator() + .isSkipText("2") + .isTopText("1"); + + testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='2')", "$expand=olingo.odata.test1.ETBaseTwoKeyNav" + + "/NavPropertyETTwoKeyNavMany/olingo.odata.test1.ETTwoBaseTwoKeyNav($select=PropertyString)") + .isKind(UriInfoKind.resource).goPath() + .first() + .isKeyPredicate(0, "PropertyInt16", "1") + .isKeyPredicate(1, "PropertyString", "'2'") + .goUpUriValidator().goExpand().first() + .isExpandStartType(EntityTypeProvider.nameETBaseTwoKeyNav) + .goPath().first() + .isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .isType(EntityTypeProvider.nameETTwoKeyNav) + .isTypeFilterOnCollection(EntityTypeProvider.nameETTwoBaseTwoKeyNav) + .goUpExpandValidator() + .goSelectItem(0).isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false); + + testUri.run("ESKeyNav", "$expand=NavPropertyETKeyNavOne($expand=NavPropertyETKeyNavMany(" + + "$expand=NavPropertyETKeyNavOne))") + .isKind(UriInfoKind.resource) + .goExpand().first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav) + .goUpExpandValidator() + .goExpand().first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true) + .isType(EntityTypeProvider.nameETKeyNav) + .goUpExpandValidator() + .goExpand().first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav); + + testUri.run("ESKeyNav", "$expand=NavPropertyETKeyNavOne($select=PropertyInt16)") + .isKind(UriInfoKind.resource) + .goExpand().first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav) + .goUpExpandValidator() + .goSelectItem(0).isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + + testUri.run("ESKeyNav", "$expand=NavPropertyETKeyNavOne($select=PropertyCompNav/PropertyInt16)") + .isKind(UriInfoKind.resource) + .goExpand().first() + .goPath().first() + .isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false) + .isType(EntityTypeProvider.nameETKeyNav) + .goUpExpandValidator() + .goSelectItem(0) + .first().isComplexProperty("PropertyCompNav", ComplexTypeProvider.nameCTNavFiveProp, false) + .n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false); + + testUri.runEx("ESKeyNav", "$expand=undefined") + .isExSemantic(MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE); + testUri.runEx("ESTwoKeyNav", "$expand=PropertyCompNav/undefined") + .isExSemantic(MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE); + testUri.runEx("ESTwoKeyNav", "$expand=PropertyCompNav/*+") + .isExSyntax(UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION); + } + + @Test + public void duplicatedSystemQueryOptionsInExpand() throws Exception { + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($select=PropertyInt16;$select=PropertyInt16)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($filter=true;$filter=true)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($orderby=PropertyInt16;$orderby=PropertyInt16)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($levels=2;$levels=3)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($expand=*;$expand=*)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($count=true;$count=true)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($top=1;$top=1)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($skip=2;$skip=2)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + + testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($search=Test;$search=Test)") + .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); + } + + @Test + public void simpleKeyInExpandSystemQueryOption() throws Exception { + testUri.runEx("ESAllPrim(0)", "$expand=NavPropertyETTwoPrimMany(-365)($filter=PropertyString eq 'Test String1')") + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); + } + + @Test + public void compoundKeyInExpandSystemQueryOption() throws Exception { + testUri.runEx("ESAllPrim(0)", "$expand=NavPropertyETTwoPrimMany(PropertyInt16=1,PropertyString=2)" + + "($filter=PropertyString eq 'Test String1')") + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); + } + + @Test + public void keyPredicatesInExpandFilter() throws Exception { + testUri.run("ESKeyNav(0)", "$expand=NavPropertyETTwoKeyNavMany($filter=NavPropertyETTwoKeyNavMany" + + "(PropertyInt16=1,PropertyString='2')/PropertyInt16 eq 1)").goExpand() + .first().goPath().isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .goUpExpandValidator().goFilter() + .is("<<NavPropertyETTwoKeyNavMany/PropertyInt16> eq <1>>"); + } + + @Test + public void keyPredicatesInDoubleExpandedFilter() throws Exception { + testUri.run("ESKeyNav(0)", "$expand=NavPropertyETTwoKeyNavMany($expand=NavPropertyETTwoKeyNavMany" + + "($filter=NavPropertyETTwoKeyNavMany(PropertyInt16=1,PropertyString='2')/PropertyInt16 eq 1))") + .goExpand() + .first().goPath().isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .goUpExpandValidator().goExpand() + .first().goPath().isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) + .goUpExpandValidator().goFilter() + .is("<<NavPropertyETTwoKeyNavMany/PropertyInt16> eq <1>>"); + } +}
