You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2016/01/12 14:08:15 UTC
[14/30] olingo-odata4 git commit: [OLINGO-834] Type checks in
ExpressionParser
[OLINGO-834] Type checks in ExpressionParser
Signed-off-by: Christian Amend <ch...@sap.com>
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/master
Commit: 104ecf43d255722e99e18641a1444e33a0ff11b1
Parents: 2f3bc28
Author: Klaus Straubinger <kl...@sap.com>
Authored: Wed Dec 16 16:14:12 2015 +0100
Committer: Christian Amend <ch...@sap.com>
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()));
}
}