Repository: olingo-odata4 Updated Branches: refs/heads/OLINGO-834_Filter_Parser 2f3bc2866 -> 104ecf43d
[OLINGO-834] Type checks in ExpressionParser 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/104ecf43 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/104ecf43 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/104ecf43 Branch: refs/heads/OLINGO-834_Filter_Parser Commit: 104ecf43d255722e99e18641a1444e33a0ff11b1 Parents: 2f3bc28 Author: Klaus Straubinger <[email protected]> Authored: Wed Dec 16 16:14:12 2015 +0100 Committer: Christian Amend <[email protected]> Committed: Wed Dec 16 16:26:51 2015 +0100 ---------------------------------------------------------------------- .../core/uri/parser/ExpressionParser.java | 413 +++++++++++++++---- .../core/uri/parser/UriParseTreeVisitor.java | 24 +- .../server/core/uri/parser/UriTokenizer.java | 5 + .../uri/queryoption/expression/BinaryImpl.java | 10 +- .../uri/queryoption/expression/MethodImpl.java | 70 ++++ .../uri/queryoption/expression/UnaryImpl.java | 9 +- .../core/uri/parser/ExpressionParserTest.java | 142 ++++++- .../core/uri/parser/UriTokenizerTest.java | 6 +- .../queryoption/expression/ExpressionTest.java | 64 +-- 9 files changed, 616 insertions(+), 127 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/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 3b04089..61c023d 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 @@ -24,13 +24,26 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; -import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.EdmTypeDefinition; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.server.api.OData; +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; +import org.apache.olingo.server.api.uri.queryoption.expression.Enumeration; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; +import org.apache.olingo.server.api.uri.queryoption.expression.LambdaRef; +import org.apache.olingo.server.api.uri.queryoption.expression.Literal; +import org.apache.olingo.server.api.uri.queryoption.expression.Member; import org.apache.olingo.server.api.uri.queryoption.expression.Method; import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind; +import org.apache.olingo.server.api.uri.queryoption.expression.TypeLiteral; +import org.apache.olingo.server.api.uri.queryoption.expression.Unary; import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; import org.apache.olingo.server.core.uri.queryoption.expression.AliasImpl; @@ -72,10 +85,10 @@ public class ExpressionParser { tokenToUnaryOperator = Collections.unmodifiableMap(temp); } + // 'cast' and 'isof' are handled specially. private static final Map<TokenKind, MethodKind> tokenToMethod; static { Map<TokenKind, MethodKind> temp = new HashMap<TokenKind, MethodKind>(); - temp.put(TokenKind.CastMethod, MethodKind.CAST); temp.put(TokenKind.CeilingMethod, MethodKind.CEILING); temp.put(TokenKind.ConcatMethod, MethodKind.CONCAT); temp.put(TokenKind.ContainsMethod, MethodKind.CONTAINS); @@ -89,7 +102,6 @@ public class ExpressionParser { temp.put(TokenKind.GeoLengthMethod, MethodKind.GEOLENGTH); temp.put(TokenKind.HourMethod, MethodKind.HOUR); temp.put(TokenKind.IndexofMethod, MethodKind.INDEXOF); - temp.put(TokenKind.IsofMethod, MethodKind.ISOF); temp.put(TokenKind.LengthMethod, MethodKind.LENGTH); temp.put(TokenKind.MaxdatetimeMethod, MethodKind.MAXDATETIME); temp.put(TokenKind.MindatetimeMethod, MethodKind.MINDATETIME); @@ -131,8 +143,16 @@ public class ExpressionParser { tokenToPrimitiveType = Collections.unmodifiableMap(temp); } + private final Edm edm; + private final OData odata; + private UriTokenizer tokenizer; + public ExpressionParser(final Edm edm, final OData odata) { + this.edm = edm; + this.odata = odata; + } + public Expression parse(UriTokenizer tokenizer) throws UriParserException { // Initialize tokenizer. this.tokenizer = tokenizer; @@ -144,7 +164,10 @@ public class ExpressionParser { Expression left = parseAnd(); while (tokenizer.next(TokenKind.OrOperator)) { final Expression right = parseAnd(); - left = new BinaryImpl(left, BinaryOperatorKind.OR, right); + checkType(left, EdmPrimitiveTypeKind.Boolean); + checkType(right, EdmPrimitiveTypeKind.Boolean); + left = new BinaryImpl(left, BinaryOperatorKind.OR, right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); } return left; } @@ -153,7 +176,10 @@ public class ExpressionParser { Expression left = parseExprEquality(); while (tokenizer.next(TokenKind.AndOperator)) { final Expression right = parseExprEquality(); - left = new BinaryImpl(left, BinaryOperatorKind.AND, right); + checkType(left, EdmPrimitiveTypeKind.Boolean); + checkType(right, EdmPrimitiveTypeKind.Boolean); + left = new BinaryImpl(left, BinaryOperatorKind.AND, right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); } return left; } @@ -164,12 +190,15 @@ public class ExpressionParser { // Null for everything other than EQ or NE while (operatorTokenKind != null) { final Expression right = parseExprEquality(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + checkEqualityTypes(left, right); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.EqualsOperator, TokenKind.NotEqualsOperator); } return left; } + // TODO: The 'isof' method has relational precedence and should appear here. private Expression parseExprRel() throws UriParserException { Expression left = parseExprAdd(); TokenKind operatorTokenKind = ParserHelper.next(tokenizer, @@ -178,7 +207,9 @@ public class ExpressionParser { // Null for everything other than GT or GE or LT or LE while (operatorTokenKind != null) { final Expression right = parseExprAdd(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + checkRelationTypes(left, right); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.GreaterThanOperator, TokenKind.GreaterThanOrEqualsOperator, TokenKind.LessThanOperator, TokenKind.LessThanOrEqualsOperator); @@ -192,7 +223,9 @@ public class ExpressionParser { // Null for everything other than ADD or SUB while (operatorTokenKind != null) { final Expression right = parseExprMul(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + checkAddSubTypes(left, right, operatorTokenKind == TokenKind.AddOperator); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double)); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.AddOperator, TokenKind.SubOperator); } return left; @@ -205,28 +238,61 @@ public class ExpressionParser { // Null for everything other than MUL or DIV or MOD while (operatorTokenKind != null) { final Expression right = parseExprUnary(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + checkType(left, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double); + checkType(right, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double)); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.MulOperator, TokenKind.DivOperator, TokenKind.ModOperator); } return left; } + // TODO: The 'cast' method has unary precedence and should appear here. private Expression parseExprUnary() throws UriParserException { Expression left = null; TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.MINUS, TokenKind.NotOperator); // Null for everything other than - or NOT while (operatorTokenKind != null) { - final Expression expression = parseExprValue(); - left = new UnaryImpl(tokenToUnaryOperator.get(operatorTokenKind), expression); + final Expression expression = parseExprPrimary(); + if (operatorTokenKind == TokenKind.NotOperator) { + checkType(expression, EdmPrimitiveTypeKind.Boolean); + } else { + checkType(expression, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double, + EdmPrimitiveTypeKind.Duration); + } + left = new UnaryImpl(tokenToUnaryOperator.get(operatorTokenKind), expression, getType(expression)); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.MINUS, TokenKind.NotOperator); } if (left == null) { - left = parseExprValue(); + left = parseExprPrimary(); } return left; } + private Expression parseExprPrimary() throws UriParserException { + final Expression left = parseExprValue(); + if (isEnumType(left) && tokenizer.next(TokenKind.HasOperator)) { + ParserHelper.requireNext(tokenizer, TokenKind.EnumValue); + final String primitiveValueLiteral = tokenizer.getText(); + final Expression right = new LiteralImpl(primitiveValueLiteral, getEnumType(primitiveValueLiteral)); + checkEnumLiteral(right); + return new BinaryImpl(left, BinaryOperatorKind.HAS, right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); + } else { + return left; + } + } + private Expression parseExprValue() throws UriParserException { if (tokenizer.next(TokenKind.OPEN)) { final Expression expression = parseExpression(); @@ -251,44 +317,34 @@ public class ExpressionParser { // TODO: Consume $it expression. } - if (tokenizer.next(TokenKind.QualifiedName)) { - // TODO: Consume typecast or bound-function expression. - } - TokenKind nextPrimitive = ParserHelper.nextPrimitive(tokenizer); if (nextPrimitive != null) { + final String primitiveValueLiteral = tokenizer.getText(); final EdmPrimitiveTypeKind primitiveTypeKind = tokenToPrimitiveType.get(nextPrimitive); EdmPrimitiveType type; if (primitiveTypeKind == null) { if (nextPrimitive == TokenKind.EnumValue) { - // TODO: Get enum type. - type = null; + type = getEnumType(primitiveValueLiteral); } else { // Null handling type = null; } } else { - type = EdmPrimitiveTypeFactory.getInstance(primitiveTypeKind); + type = odata.createPrimitiveTypeInstance(primitiveTypeKind); } - return new LiteralImpl(tokenizer.getText(), type); + return new LiteralImpl(primitiveValueLiteral, type); } + // The method token text includes the opening parenthesis so that method calls can be recognized unambiguously. + // OData identifiers have to be considered after that. TokenKind nextMethod = nextMethod(); if (nextMethod != null) { MethodKind methodKind = tokenToMethod.get(nextMethod); - List<Expression> parameters = new ArrayList<Expression>(); - // The method token text includes the opening parenthesis! - if (!tokenizer.next(TokenKind.CLOSE)) { - do { - parameters.add(parseExpression()); - } while (tokenizer.next(TokenKind.COMMA)); - ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); - } - - MethodImpl methodImpl = new MethodImpl(methodKind, parameters); - validateMethodParameters(methodImpl); + return new MethodImpl(methodKind, parseMethodParameters(methodKind)); + } - return methodImpl; + if (tokenizer.next(TokenKind.QualifiedName)) { + // TODO: Consume typecast or bound-function expression. } if (tokenizer.next(TokenKind.ODataIdentifier)) { @@ -298,85 +354,131 @@ public class ExpressionParser { throw new UriParserSyntaxException("Unexpected token", UriParserSyntaxException.MessageKeys.SYNTAX); } - private void validateMethodParameters(final Method method) throws UriParserException { - // We might validate parameter types in the future. - int size = method.getParameters().size(); - switch (method.getMethod()) { - // Must have two Parameters. - case CONTAINS: - case ENDSWITH: - case STARTSWITH: - case INDEXOF: - case CONCAT: - case GEODISTANCE: - case GEOINTERSECTS: - if (size != 2) { - throw new UriParserSemanticException( - "The method " + method.getMethod() + " needs exactly two parameters.", - null); // TODO: message key - } + private List<Expression> parseMethodParameters(final MethodKind methodKind) throws UriParserException { + List<Expression> parameters = new ArrayList<Expression>(); + switch (methodKind) { + // Must have no parameter. + case NOW: + case MAXDATETIME: + case MINDATETIME: break; + // Must have one parameter. case LENGTH: case TOLOWER: case TOUPPER: case TRIM: + final Expression stringParameter = parseExpression(); + checkType(stringParameter, EdmPrimitiveTypeKind.String); + parameters.add(stringParameter); + break; case YEAR: case MONTH: case DAY: + final Expression dateParameter = parseExpression(); + checkType(dateParameter, EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.DateTimeOffset); + parameters.add(dateParameter); + break; case HOUR: case MINUTE: case SECOND: case FRACTIONALSECONDS: + final Expression timeParameter = parseExpression(); + checkType(timeParameter, EdmPrimitiveTypeKind.TimeOfDay, EdmPrimitiveTypeKind.DateTimeOffset); + parameters.add(timeParameter); + break; case DATE: case TIME: case TOTALOFFSETMINUTES: + final Expression dateTimeParameter = parseExpression(); + checkType(dateTimeParameter, EdmPrimitiveTypeKind.DateTimeOffset); + parameters.add(dateTimeParameter); + break; case TOTALSECONDS: + final Expression durationParameter = parseExpression(); + checkType(durationParameter, EdmPrimitiveTypeKind.Duration); + parameters.add(durationParameter); + break; case ROUND: case FLOOR: case CEILING: + final Expression decimalParameter = parseExpression(); + checkType(decimalParameter, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double); + parameters.add(decimalParameter); + break; case GEOLENGTH: - if (size != 1) { - throw new UriParserSemanticException( - "The method '" + method.getMethod() + "' needs exactly one parameter.", - null); // TODO: message key - } + final Expression geoParameter = parseExpression(); + checkType(geoParameter, + EdmPrimitiveTypeKind.GeographyLineString, EdmPrimitiveTypeKind.GeometryLineString); + parameters.add(geoParameter); break; - // Must have no parameter. - case NOW: - case MAXDATETIME: - case MINDATETIME: - if (size > 0) { - throw new UriParserSemanticException("The method '" + method.getMethod() + "' must have no parameters.", - null); // TODO: message key - } + + // Must have two parameters. + case CONTAINS: + case ENDSWITH: + case STARTSWITH: + case INDEXOF: + case CONCAT: + final Expression stringParameter1 = parseExpression(); + checkType(stringParameter1, EdmPrimitiveTypeKind.String); + parameters.add(stringParameter1); + ParserHelper.requireNext(tokenizer, TokenKind.COMMA); + final Expression stringParameter2 = parseExpression(); + checkType(stringParameter2, EdmPrimitiveTypeKind.String); + parameters.add(stringParameter2); break; - // Variable parameter number - case CAST: - case ISOF: - if (size < 1 || size > 2) { - throw new UriParserSemanticException( - "The method '" + method.getMethod() + "' must have one or two parameters.", - null); // TODO: message key - } + case GEODISTANCE: + final Expression geoParameter1 = parseExpression(); + checkType(geoParameter1, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint); + parameters.add(geoParameter1); + ParserHelper.requireNext(tokenizer, TokenKind.COMMA); + final Expression geoParameter2 = parseExpression(); + checkType(geoParameter2, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint); + parameters.add(geoParameter2); + break; + case GEOINTERSECTS: + final Expression geoPointParameter = parseExpression(); + checkType(geoPointParameter, + EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint); + parameters.add(geoPointParameter); + ParserHelper.requireNext(tokenizer, TokenKind.COMMA); + final Expression geoPolygonParameter = parseExpression(); + checkType(geoPolygonParameter, + EdmPrimitiveTypeKind.GeographyPolygon, EdmPrimitiveTypeKind.GeometryPolygon); + parameters.add(geoPolygonParameter); break; + + // Can have two or three parameters. case SUBSTRING: - if (size < 2 || size > 3) { - throw new UriParserSemanticException( - "The method '" + method.getMethod() + "' must have two or three parameters.", - null); // TODO: message key + final Expression parameterFirst = parseExpression(); + checkType(parameterFirst, EdmPrimitiveTypeKind.String); + parameters.add(parameterFirst); + ParserHelper.requireNext(tokenizer, TokenKind.COMMA); + final Expression parameterSecond = parseExpression(); + checkType(parameterSecond, + EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int16, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte); + parameters.add(parameterSecond); + if (tokenizer.next(TokenKind.COMMA)) { + final Expression parameterThird = parseExpression(); + checkType(parameterThird, + EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int16, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte); + parameters.add(parameterThird); } break; + default: - throw new UriParserSemanticException( - "Unkown method '" + method.getMethod() + "'", - null); // TODO: message key + throw new UriParserSemanticException("Unkown method '" + methodKind.name() + "'", + UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, methodKind.name()); // TODO: better message } + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + return parameters; } private TokenKind nextMethod() { return ParserHelper.next(tokenizer, - TokenKind.CastMethod, TokenKind.CeilingMethod, TokenKind.ConcatMethod, TokenKind.ContainsMethod, @@ -390,7 +492,6 @@ public class ExpressionParser { TokenKind.GeoLengthMethod, TokenKind.HourMethod, TokenKind.IndexofMethod, - TokenKind.IsofMethod, TokenKind.LengthMethod, TokenKind.MaxdatetimeMethod, TokenKind.MindatetimeMethod, @@ -409,4 +510,158 @@ public class ExpressionParser { TokenKind.TrimMethod, TokenKind.YearMethod); } + + private EdmType getType(final Expression expression) throws UriParserException { + EdmType type; + if (expression instanceof Literal) { + type = ((Literal) expression).getType(); + } else if (expression instanceof TypeLiteral) { + type = ((TypeLiteral) expression).getType(); + } else if (expression instanceof Enumeration) { + type = ((Enumeration) expression).getType(); + } else if (expression instanceof Member) { + type = ((Member) expression).getType(); + } else if (expression instanceof Unary) { + type = ((UnaryImpl) expression).getType(); + } else if (expression instanceof Binary) { + type = ((BinaryImpl) expression).getType(); + } else if (expression instanceof Method) { + type = ((MethodImpl) expression).getType(); + } else if (expression instanceof LambdaRef) { + throw new UriParserSemanticException("Type determination not implemented.", + UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, expression.toString()); + } else if (expression instanceof Alias) { + type = null; // The alias would have to be available already parsed. + } else { + throw new UriParserSemanticException("Unknown expression type.", + UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, expression.toString()); + } + if (type != null && type.getKind() == EdmTypeKind.DEFINITION) { + type = ((EdmTypeDefinition) type).getUnderlyingType(); + } + return type; + } + + private boolean isType(final Expression expression, final EdmPrimitiveTypeKind... kinds) throws UriParserException { + final EdmType expressionType = getType(expression); + if (expressionType == null) { + return true; + } + for (final EdmPrimitiveTypeKind kind : kinds) { + if (expressionType.equals(odata.createPrimitiveTypeInstance(kind))) { + return true; + } + } + return false; + } + + private void checkType(final Expression expression, final EdmPrimitiveTypeKind... kinds) throws UriParserException { + if (!isType(expression, kinds)) { + throw new UriParserSemanticException("Incompatible type.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, // TODO: better message + getType(expression) == null ? + "" : + getType(expression).getFullQualifiedName().getFullQualifiedNameAsString()); + } + } + + private void checkEqualityTypes(final Expression left, final Expression right) throws UriParserException { + final EdmType leftType = getType(left); + final EdmType rightType = getType(right); + if (leftType == null || rightType == null || leftType.equals(rightType)) { + return; + } + if (leftType.getKind() != EdmTypeKind.PRIMITIVE + || rightType.getKind() != EdmTypeKind.PRIMITIVE + || !(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType) + || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) { + throw new UriParserSemanticException("Incompatible types.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, ""); // TODO: better message + } + } + + private EdmPrimitiveType getEnumType(final String primitiveValueLiteral) throws UriParserException { + final String enumTypeName = primitiveValueLiteral.substring(0, primitiveValueLiteral.indexOf('\'')); + final EdmPrimitiveType type = edm.getEnumType(new FullQualifiedName(enumTypeName)); + if (type == null) { + throw new UriParserSemanticException("Unknown Enum type '" + enumTypeName + "'.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, enumTypeName); + } + return type; + } + + private boolean isEnumType(final Expression expression) throws UriParserException { + final EdmType expressionType = getType(expression); + return expressionType == null + || expressionType.getKind() == EdmTypeKind.ENUM + || isType(expression, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte); + } + + private void checkEnumLiteral(final Expression expression) throws UriParserException { + if (expression == null + || !(expression instanceof Literal) + || ((Literal) expression).getType() == null + || ((Literal) expression).getType().getKind() != EdmTypeKind.ENUM) { + throw new UriParserSemanticException("Enum literal expected.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, ""); // TODO: better message + } + } + + private void checkRelationTypes(final Expression left, final Expression right) throws UriParserException { + 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, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double, + EdmPrimitiveTypeKind.Boolean, EdmPrimitiveTypeKind.Guid, EdmPrimitiveTypeKind.String, + EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.TimeOfDay, + EdmPrimitiveTypeKind.DateTimeOffset, EdmPrimitiveTypeKind.Duration); + checkType(right, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double, + EdmPrimitiveTypeKind.Boolean, EdmPrimitiveTypeKind.Guid, EdmPrimitiveTypeKind.String, + EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.TimeOfDay, + EdmPrimitiveTypeKind.DateTimeOffset, EdmPrimitiveTypeKind.Duration); + if (!(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType) + || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) { + throw new UriParserSemanticException("Incompatible types.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, ""); // TODO: better message + } + } + + private void checkAddSubTypes(final Expression left, final Expression right, final boolean isAdd) + throws UriParserException { + final EdmType leftType = getType(left); + final EdmType rightType = getType(right); + if (leftType == null || rightType == null + || isType(left, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double) + && isType(right, + EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, + EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)) { + return; + } + if (isType(left, EdmPrimitiveTypeKind.DateTimeOffset) + && (isType(right, EdmPrimitiveTypeKind.Duration) + || isType(right, EdmPrimitiveTypeKind.DateTimeOffset) && !isAdd)) { + return; + } + if (isType(left, EdmPrimitiveTypeKind.Duration) && isType(right, EdmPrimitiveTypeKind.Duration) + || isType(left, EdmPrimitiveTypeKind.Date) + && (isType(right, EdmPrimitiveTypeKind.Duration) || isType(right, EdmPrimitiveTypeKind.Date) && !isAdd)) { + return; + } + throw new UriParserSemanticException("Incompatible types.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, ""); // TODO: better message + } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java index 3f7f70c..9d29ab2 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java @@ -738,7 +738,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), tokenIndex == UriLexer.ADD ? BinaryOperatorKind.ADD : BinaryOperatorKind.SUB, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + null); } @Override @@ -785,7 +786,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), BinaryOperatorKind.AND, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)); } @Override @@ -815,7 +817,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), kind, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)); } @Override @@ -843,7 +846,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), tokenIndex == UriLexer.EQ_ALPHA ? BinaryOperatorKind.EQ : BinaryOperatorKind.NE, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)); } @Override @@ -851,7 +855,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), BinaryOperatorKind.HAS, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)); } @Override @@ -875,7 +880,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), kind, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + null); } @Override @@ -883,7 +889,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { return new BinaryImpl( (Expression) ctx.vE1.accept(this), BinaryOperatorKind.OR, - (Expression) ctx.vE2.accept(this)); + (Expression) ctx.vE2.accept(this), + EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)); } @Override @@ -2294,7 +2301,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> { public Expression visitAltUnary(@NotNull final UriParserParser.AltUnaryContext ctx) { return new UnaryImpl( ctx.unary().NOT() == null ? UnaryOperatorKind.MINUS : UnaryOperatorKind.NOT, - (Expression) ctx.commonExpr().accept(this)); + (Expression) ctx.commonExpr().accept(this), + null); } @Override http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java index a40f4ec..4b43cd9 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java @@ -80,6 +80,7 @@ public class UriTokenizer { GreaterThanOrEqualsOperator, LessThanOperator, LessThanOrEqualsOperator, + HasOperator, AddOperator, SubOperator, MulOperator, @@ -140,6 +141,7 @@ public class UriTokenizer { * The order in which this method is called with different token kinds is important, * not only for performance reasons but also if tokens can start with the same characters * (e.g., a qualified name starts with an OData identifier). + * The index is advanced to the end of this token if the token is found. * @param allowedTokenKind the kind of token to expect * @return <code>true</code> if the token is found; <code>false</code> otherwise * @see #getText() @@ -288,6 +290,9 @@ public class UriTokenizer { case LessThanOrEqualsOperator: found = nextBinaryOperator("le"); break; + case HasOperator: + found = nextBinaryOperator("has"); + break; case AddOperator: found = nextBinaryOperator("add"); break; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java index 3f2e8f2..2439bcf 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java @@ -18,6 +18,7 @@ */ 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.queryoption.expression.Binary; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; @@ -30,11 +31,14 @@ public class BinaryImpl implements Binary { private final Expression left; private final BinaryOperatorKind operator; private final Expression right; + private final EdmType type; - public BinaryImpl(final Expression left, final BinaryOperatorKind operator, final Expression right) { + public BinaryImpl(final Expression left, final BinaryOperatorKind operator, final Expression right, + final EdmType type) { this.left = left; this.operator = operator; this.right = right; + this.type = type; } @Override @@ -52,6 +56,10 @@ public class BinaryImpl implements Binary { return right; } + public EdmType getType() { + return type; + } + @Override public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException { T left = this.left.accept(visitor); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java ---------------------------------------------------------------------- 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 1c8ce64..0346292 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 @@ -22,12 +22,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.server.api.ODataApplicationException; 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; import org.apache.olingo.server.api.uri.queryoption.expression.Method; import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind; +import org.apache.olingo.server.api.uri.queryoption.expression.TypeLiteral; +import org.apache.olingo.server.core.ODataImpl; public class MethodImpl implements Method { @@ -44,6 +48,72 @@ public class MethodImpl implements Method { return method; } + public EdmType getType() { + EdmPrimitiveTypeKind kind = null; + switch (method) { + case CONTAINS: + case STARTSWITH: + case ENDSWITH: + kind = EdmPrimitiveTypeKind.Boolean; + break; + case LENGTH: + case INDEXOF: + kind = EdmPrimitiveTypeKind.Int32; + break; + case SUBSTRING: + case TOLOWER: + case TOUPPER: + case TRIM: + case CONCAT: + kind = EdmPrimitiveTypeKind.String; + break; + case YEAR: + case MONTH: + case DAY: + case HOUR: + case MINUTE: + case SECOND: + kind = EdmPrimitiveTypeKind.Int32; + break; + case FRACTIONALSECONDS: + case TOTALSECONDS: + kind = EdmPrimitiveTypeKind.Decimal; + break; + case DATE: + kind = EdmPrimitiveTypeKind.Date; + break; + case TIME: + kind = EdmPrimitiveTypeKind.TimeOfDay; + break; + case TOTALOFFSETMINUTES: + kind = EdmPrimitiveTypeKind.Int32; + break; + case MINDATETIME: + case MAXDATETIME: + case NOW: + kind = EdmPrimitiveTypeKind.DateTimeOffset; + break; + case ROUND: + case FLOOR: + case CEILING: + kind = EdmPrimitiveTypeKind.Double; // Needs to be refined if Decimal must be distinguished from Double. + break; + case GEODISTANCE: + case GEOLENGTH: + kind = EdmPrimitiveTypeKind.Double; + break; + case GEOINTERSECTS: + kind = EdmPrimitiveTypeKind.Boolean; + break; + case CAST: + return ((TypeLiteral) parameters.get(parameters.size() - 1)).getType(); + case ISOF: + kind = EdmPrimitiveTypeKind.Boolean; + break; + } + return new ODataImpl().createPrimitiveTypeInstance(kind); + } + @Override public List<Expression> getParameters() { return parameters == null ? http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java index 910997e..86639a6 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java @@ -18,6 +18,7 @@ */ 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.queryoption.expression.Expression; import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; @@ -29,10 +30,12 @@ public class UnaryImpl implements Unary { private final UnaryOperatorKind operator; private final Expression expression; + private final EdmType type; - public UnaryImpl(final UnaryOperatorKind operator, final Expression expression) { + public UnaryImpl(final UnaryOperatorKind operator, final Expression expression, final EdmType type) { this.operator = operator; this.expression = expression; + this.type = type; } @Override @@ -45,6 +48,10 @@ public class UnaryImpl implements Unary { return expression; } + public EdmType getType() { + return type; + } + @Override public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException { T operand = expression.accept(visitor); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java index 58f2a1f..183ff22 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java @@ -21,15 +21,19 @@ package org.apache.olingo.server.core.uri.parser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.Locale; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; import org.junit.Test; public class ExpressionParserTest { + private final OData odata = OData.newInstance(); + @Test public void equality() throws Exception { Expression expression = parseExpression("5 eq 5"); @@ -37,6 +41,12 @@ public class ExpressionParserTest { expression = parseExpression("5 ne 5"); assertEquals("{5 NE 5}", expression.toString()); + + assertEquals("{1 EQ null}", parseExpression("1 eq null").toString()); + assertEquals("{null NE 2}", parseExpression("null ne 2").toString()); + assertEquals("{null EQ null}", parseExpression("null eq null").toString()); + + wrongExpression("5 eq '5'"); } @Test @@ -52,6 +62,14 @@ public class ExpressionParserTest { expression = parseExpression("5 le 5"); assertEquals("{5 LE 5}", expression.toString()); + + assertEquals("{5 LE 5.1}", parseExpression("5 le 5.1").toString()); + + assertEquals("{1 GT null}", parseExpression("1 gt null").toString()); + assertEquals("{null GE 2}", parseExpression("null ge 2").toString()); + assertEquals("{null LE null}", parseExpression("null le null").toString()); + + wrongExpression("5 gt duration'PT5H'"); } @Test @@ -59,8 +77,37 @@ public class ExpressionParserTest { Expression expression = parseExpression("5 add 5"); assertEquals("{5 ADD 5}", expression.toString()); - expression = parseExpression("5 sub 5"); - assertEquals("{5 SUB 5}", expression.toString()); + expression = parseExpression("5 sub 5.1"); + assertEquals("{5 SUB 5.1}", expression.toString()); + + expression = parseExpression("2000-02-29 sub 2016-02-29"); + assertEquals("{2000-02-29 SUB 2016-02-29}", expression.toString()); + + expression = parseExpression("2000-02-29T00:00:00Z sub 2016-02-29T01:02:03Z"); + assertEquals("{2000-02-29T00:00:00Z SUB 2016-02-29T01:02:03Z}", expression.toString()); + + expression = parseExpression("duration'PT1H' add duration'PT1M'"); + assertEquals("{duration'PT1H' ADD duration'PT1M'}", expression.toString()); + + expression = parseExpression("2016-01-01 add duration'P60D'"); + assertEquals("{2016-01-01 ADD duration'P60D'}", expression.toString()); + + expression = parseExpression("2000-02-29T00:00:00Z add duration'PT12H'"); + assertEquals("{2000-02-29T00:00:00Z ADD duration'PT12H'}", expression.toString()); + + assertEquals("{1 ADD null}", parseExpression("1 add null").toString()); + assertEquals("{null ADD 2}", parseExpression("null add 2").toString()); + assertEquals("{null SUB null}", parseExpression("null sub null").toString()); + + wrongExpression("1 add '2'"); + wrongExpression("'1' add 2"); + wrongExpression("1 add 2000-02-29"); + wrongExpression("11:12:13 sub 2000-02-29T11:12:13Z"); + wrongExpression("2000-02-29 add 2016-02-29"); + wrongExpression("2000-02-29T00:00:00Z add 2016-02-29T01:02:03Z"); + wrongExpression("2000-02-29T00:00:00Z add 1"); + wrongExpression("2000-02-29 sub 1"); + wrongExpression("duration'P7D' add 2000-02-29"); } @Test @@ -73,6 +120,8 @@ public class ExpressionParserTest { expression = parseExpression("5 mod 5"); assertEquals("{5 MOD 5}", expression.toString()); + + wrongExpression("1 mod '2'"); } @Test @@ -81,9 +130,12 @@ public class ExpressionParserTest { assertEquals("{MINUS 5}", expression.toString()); assertEquals("{MINUS -1}", parseExpression("--1").toString()); + assertEquals("{MINUS duration'PT1M'}", parseExpression("-duration'PT1M'").toString()); + + expression = parseExpression("not false"); + assertEquals("{NOT false}", expression.toString()); - expression = parseExpression("not 5"); - assertEquals("{NOT 5}", expression.toString()); + wrongExpression("-11:12:13"); } @Test @@ -111,6 +163,8 @@ public class ExpressionParserTest { expression = parseMethod(TokenKind.MindatetimeMethod); assertEquals("{mindatetime []}", expression.toString()); + + wrongExpression("now(1)"); } @Test @@ -148,16 +202,79 @@ public class ExpressionParserTest { expression = parseMethod(TokenKind.SecondMethod, dateTimeOffsetValue); assertEquals("{second [" + dateTimeOffsetValue + "]}", expression.toString()); + + expression = parseMethod(TokenKind.DateMethod, dateTimeOffsetValue); + assertEquals("{date [" + dateTimeOffsetValue + "]}", expression.toString()); + + expression = parseMethod(TokenKind.TotalsecondsMethod, "duration'PT1H'"); + assertEquals("{totalseconds [duration'PT1H']}", expression.toString()); + + expression = parseMethod(TokenKind.RoundMethod, "3.141592653589793"); + assertEquals("{round [3.141592653589793]}", expression.toString()); + + assertEquals("{hour [null]}", parseMethod(TokenKind.HourMethod, new String[] { null }).toString()); + + wrongExpression("trim()"); + wrongExpression("trim(1)"); + wrongExpression("ceiling('1.2')"); + } + + @Test + public void twoParameterMethods() throws Exception { + Expression expression = parseMethod(TokenKind.ContainsMethod, "'a'", "'b'"); + assertEquals("{contains ['a', 'b']}", expression.toString()); + + expression = parseMethod(TokenKind.EndswithMethod, "'a'", "'b'"); + assertEquals("{endswith ['a', 'b']}", expression.toString()); + + expression = parseMethod(TokenKind.StartswithMethod, "'a'", "'b'"); + assertEquals("{startswith ['a', 'b']}", expression.toString()); + + expression = parseMethod(TokenKind.IndexofMethod, "'a'", "'b'"); + assertEquals("{indexof ['a', 'b']}", expression.toString()); + + expression = parseMethod(TokenKind.ConcatMethod, "'a'", "'b'"); + assertEquals("{concat ['a', 'b']}", expression.toString()); + + // TODO: Geo methods. +// expression = parseMethod(TokenKind.GeoDistanceMethod, +// "geography'SRID=0;Point(1.2 3.4)'", "geography'SRID=0;Point(5.6 7.8)'"); +// assertEquals("{geo.distance [geography'SRID=0;Point(1.2 3.4)', geography'SRID=0;Point(5.6 7.8)']}", +// expression.toString()); +// +// expression = parseMethod(TokenKind.GeoIntersectsMethod); +// assertEquals("{geo.intersects []}", expression.toString()); + + assertEquals("{startswith [null, 'b']}", parseMethod(TokenKind.StartswithMethod, null, "'b'").toString()); + assertEquals("{indexof ['a', null]}", parseMethod(TokenKind.IndexofMethod, "'a'", null).toString()); + + wrongExpression("concat('a')"); + wrongExpression("endswith('a',1)"); +} + + @Test + public void variableParameterNumberMethods() throws Exception { + Expression expression = parseMethod(TokenKind.SubstringMethod, "'abc'", "1", "2"); + assertEquals("{substring ['abc', 1, 2]}", expression.toString()); + expression = parseMethod(TokenKind.SubstringMethod, "'abc'", "1"); + assertEquals("{substring ['abc', 1]}", expression.toString()); + + wrongExpression("substring('abc')"); + wrongExpression("substring('abc',1,2,3)"); + wrongExpression("substring(1,2)"); } private Expression parseMethod(TokenKind kind, String... parameters) throws UriParserException { String expressionString = kind.name().substring(0, kind.name().indexOf("Method")) .toLowerCase(Locale.ROOT).replace("geo", "geo.") + '('; - for (int i = 0; i < parameters.length; i++) { - if (i > 0) { + boolean first = true; + for (final String parameter : parameters) { + if (first) { + first = false; + } else { expressionString += ','; } - expressionString += parameters[i]; + expressionString += parameter; } expressionString += ')'; @@ -168,9 +285,18 @@ public class ExpressionParserTest { private Expression parseExpression(final String expressionString) throws UriParserException { UriTokenizer tokenizer = new UriTokenizer(expressionString); - Expression expression = new ExpressionParser().parse(tokenizer); + Expression expression = new ExpressionParser(null, odata).parse(tokenizer); assertNotNull(expression); assertTrue(tokenizer.next(TokenKind.EOF)); return expression; } + + private void wrongExpression(final String expressionString) { + try { + new ExpressionParser(null, odata).parse(new UriTokenizer(expressionString)); + fail("Expected exception not thrown."); + } catch (final UriParserException e) { + assertNotNull(e); + } + } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java index b5614ad..1b2d508 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java @@ -412,7 +412,7 @@ public class UriTokenizerTest { assertTrue(tokenizer.next(TokenKind.IntegerValue)); assertTrue(tokenizer.next(TokenKind.EOF)); - tokenizer = new UriTokenizer("1 gt 2 or 3 ge 4 or 5 lt 6"); + tokenizer = new UriTokenizer("1 gt 2 or 3 ge 4 or 5 lt 6 or 7 has namespace.name'flag1,flag2'"); assertTrue(tokenizer.next(TokenKind.IntegerValue)); assertTrue(tokenizer.next(TokenKind.GreaterThanOperator)); assertTrue(tokenizer.next(TokenKind.IntegerValue)); @@ -424,6 +424,10 @@ public class UriTokenizerTest { assertTrue(tokenizer.next(TokenKind.IntegerValue)); assertTrue(tokenizer.next(TokenKind.LessThanOperator)); assertTrue(tokenizer.next(TokenKind.IntegerValue)); + assertTrue(tokenizer.next(TokenKind.OrOperator)); + assertTrue(tokenizer.next(TokenKind.IntegerValue)); + assertTrue(tokenizer.next(TokenKind.HasOperator)); + assertTrue(tokenizer.next(TokenKind.EnumValue)); assertTrue(tokenizer.next(TokenKind.EOF)); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/104ecf43/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/queryoption/expression/ExpressionTest.java ---------------------------------------------------------------------- diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/queryoption/expression/ExpressionTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/queryoption/expression/ExpressionTest.java index ec5ce6e..864b17a 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/queryoption/expression/ExpressionTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/queryoption/expression/ExpressionTest.java @@ -31,14 +31,13 @@ import org.apache.olingo.commons.api.edm.EdmAction; import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmEnumType; import org.apache.olingo.commons.api.edm.EdmFunction; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.server.api.OData; -import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; 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.MethodKind; import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind; import org.apache.olingo.server.core.uri.UriInfoImpl; @@ -53,7 +52,8 @@ import org.apache.olingo.server.tecsvc.provider.FunctionProvider; import org.junit.Test; public class ExpressionTest { - private static final Edm edm = OData.newInstance().createServiceMetadata( + private static final OData odata = OData.newInstance(); + private static final Edm edm = odata.createServiceMetadata( new EdmTechProvider(), Collections.<EdmxReference> emptyList()).getEdm(); @Test @@ -69,7 +69,7 @@ public class ExpressionTest { } @Test - public void aliasExpression() throws ExpressionVisitException, ODataApplicationException { + public void aliasExpression() throws Exception { AliasImpl expression = new AliasImpl("Test"); assertEquals("Test", expression.getParameterName()); @@ -79,47 +79,50 @@ public class ExpressionTest { } @Test - public void binaryExpression() throws ExpressionVisitException, ODataApplicationException { - Expression expressionLeft = new LiteralImpl("A", null); - Expression expressionRight = new LiteralImpl("B", null); + public void binaryExpression() throws Exception { + Expression expressionLeft = new LiteralImpl("2", odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte)); + Expression expressionRight = new LiteralImpl("-1", odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.SByte)); - BinaryImpl expression = new BinaryImpl(expressionLeft, BinaryOperatorKind.SUB, expressionRight); + BinaryImpl expression = new BinaryImpl(expressionLeft, BinaryOperatorKind.SUB, expressionRight, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte)); assertEquals(expressionLeft, expression.getLeftOperand()); assertEquals(expressionRight, expression.getRightOperand()); assertEquals(BinaryOperatorKind.SUB, expression.getOperator()); + assertEquals(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte), expression.getType()); String output = expression.accept(new FilterTreeToText()); - assertEquals("<<A> sub <B>>", output); + assertEquals("<<2> sub <-1>>", output); } @Test - public void enumerationExpression() throws ExpressionVisitException, ODataApplicationException { + public void enumerationExpression() throws Exception { EdmEnumType type = edm.getEnumType(EnumTypeProvider.nameENString); assertNotNull(type); - EnumerationImpl expression = new EnumerationImpl(type, Arrays.asList("A", "B")); + EnumerationImpl expression = new EnumerationImpl(type, Arrays.asList("String1", "String2")); assertEquals(type, expression.getType()); - assertEquals("A", expression.getValues().get(0)); - assertEquals("B", expression.getValues().get(1)); - assertEquals("<olingo.odata.test1.ENString<A,B>>", expression.accept(new FilterTreeToText())); + assertEquals("String1", expression.getValues().get(0)); + assertEquals("String2", expression.getValues().get(1)); + assertEquals("<olingo.odata.test1.ENString<String1,String2>>", expression.accept(new FilterTreeToText())); } @Test - public void lambdaRefExpression() throws ExpressionVisitException, ODataApplicationException { + public void lambdaRefExpression() throws Exception { LambdaRefImpl expression = new LambdaRefImpl("A"); assertEquals("A", expression.getVariableName()); assertEquals("<A>", expression.accept(new FilterTreeToText())); } @Test - public void literalExpression() throws ExpressionVisitException, ODataApplicationException { - LiteralImpl expression = new LiteralImpl("A", null); - assertEquals("A", expression.getText()); - assertEquals("<A>", expression.accept(new FilterTreeToText())); + public void literalExpression() throws Exception { + LiteralImpl expression = new LiteralImpl("'A'", odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + assertEquals("'A'", expression.getText()); + assertEquals("<'A'>", expression.accept(new FilterTreeToText())); + assertEquals(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String), expression.getType()); } @Test - public void memberExpression() throws ExpressionVisitException, ODataApplicationException { + public void memberExpression() throws Exception { EdmEntityType entityType = edm.getEntityType(EntityTypeProvider.nameETKeyNav); // UriResourceImpl @@ -189,20 +192,21 @@ public class ExpressionTest { } @Test - public void methodCallExpression() throws ExpressionVisitException, ODataApplicationException { - Expression p0 = new LiteralImpl("A", null); - Expression p1 = new LiteralImpl("B", null); + public void methodCallExpression() throws Exception { + Expression p0 = new LiteralImpl("'A'", odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + Expression p1 = new LiteralImpl("'B'", odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); MethodImpl expression = new MethodImpl(MethodKind.CONCAT, Arrays.asList(p0, p1)); assertEquals(MethodKind.CONCAT, expression.getMethod()); - assertEquals("<concat(<A>,<B>)>", expression.accept(new FilterTreeToText())); + assertEquals("<concat(<'A'>,<'B'>)>", expression.accept(new FilterTreeToText())); + assertEquals(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String), expression.getType()); assertEquals(p0, expression.getParameters().get(0)); assertEquals(p1, expression.getParameters().get(1)); } @Test - public void typeLiteralExpression() throws ExpressionVisitException, ODataApplicationException { + public void typeLiteralExpression() throws Exception { EdmEntityType entityBaseType = edm.getEntityType(EntityTypeProvider.nameETBaseTwoKeyNav); TypeLiteralImpl expression = new TypeLiteralImpl(entityBaseType); @@ -211,13 +215,15 @@ public class ExpressionTest { } @Test - public void unaryExpression() throws ExpressionVisitException, ODataApplicationException { - Expression operand = new LiteralImpl("A", null); - UnaryImpl expression = new UnaryImpl(UnaryOperatorKind.MINUS, operand); + public void unaryExpression() throws Exception { + Expression operand = new LiteralImpl("1.2", odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal)); + UnaryImpl expression = new UnaryImpl(UnaryOperatorKind.MINUS, operand, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal)); assertEquals(UnaryOperatorKind.MINUS, expression.getOperator()); assertEquals(operand, expression.getOperand()); + assertEquals(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal), expression.getType()); - assertEquals("<- <A>>", expression.accept(new FilterTreeToText())); + assertEquals("<- <1.2>>", expression.accept(new FilterTreeToText())); } }
