[OLINGO-834] Type checks in ExpressionParser
Signed-off-by: Christian Amend <christian.amend@sap.com>
This commit is contained in:
parent
2f3bc2866b
commit
104ecf43d2
|
@ -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);
|
||||
}
|
||||
return new MethodImpl(methodKind, parseMethodParameters(methodKind));
|
||||
}
|
||||
|
||||
MethodImpl methodImpl = new MethodImpl(methodKind, parameters);
|
||||
validateMethodParameters(methodImpl);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 ?
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 5");
|
||||
assertEquals("{NOT 5}", expression.toString());
|
||||
expression = parseExpression("not false");
|
||||
assertEquals("{NOT false}", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue