From 415a47268fcebb5f35e527490ecdcc38047d704b Mon Sep 17 00:00:00 2001 From: Lyndon Hu Date: Thu, 11 Apr 2019 22:27:59 +0800 Subject: [PATCH] Add support for simple IN expression. --- .gitignore | 1 + .../expression/BinaryOperatorKind.java | 9 +- .../expression/ExpressionVisitor.java | 9 + .../queryoption/expression/LiteralList.java | 54 ++++++ .../core/debug/ExpressionJsonVisitor.java | 18 +- .../core/uri/parser/ExpressionParser.java | 168 ++++++++++-------- .../parser/UriParserSemanticException.java | 4 +- .../server/core/uri/parser/UriTokenizer.java | 12 +- .../expression/LiteralListImpl.java | 75 ++++++++ .../server-core-exceptions-i18n.properties | 1 + .../server/core/uri/parser/LexerTest.java | 36 ++++ .../core/uri/parser/UriTokenizerTest.java | 7 + .../expression/ExpressionVisitorImpl.java | 16 +- .../server/core/uri/parser/UriParserTest.java | 77 ++++++++ .../core/uri/testutil/FilterTreeToText.java | 33 +++- .../core/uri/testutil/FilterValidator.java | 4 + 16 files changed, 422 insertions(+), 102 deletions(-) create mode 100644 lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/LiteralList.java create mode 100644 lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralListImpl.java diff --git a/.gitignore b/.gitignore index 81878c2ccb..e18d71a3d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ nb-configuration.xml .externalToolBuilders maven-eclipse.xml dependency-reduced-pom.xml +*.iml \ No newline at end of file diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/BinaryOperatorKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/BinaryOperatorKind.java index 2ce4eeef7a..73b6454cee 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/BinaryOperatorKind.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/BinaryOperatorKind.java @@ -92,13 +92,18 @@ public enum BinaryOperatorKind { /** * Or operator */ - OR("or"); + OR("or"), + + /** + * In operator + */ + IN("in"); private String syntax; /** * Constructor for enumeration value - * @param Syntax used in the URI + * @param syntax used in the URI */ private BinaryOperatorKind(final String syntax) { this.syntax = syntax; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java index 402db3a818..841e48142d 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/ExpressionVisitor.java @@ -133,4 +133,13 @@ T visitLambdaExpression(String lambdaFunction, String lambdaVariable, Expression */ T visitEnum(EdmEnumType type, List enumValues) throws ExpressionVisitException, ODataApplicationException; + /** + * Called for each traversed {@link Literal} expression 被每个遍历的字面量集合调用 + * @param literalList literalList + * @return Application return value of type T + * @throws ExpressionVisitException Thrown if an exception while traversing occured + * @throws ODataApplicationException Thrown by the application + */ + T visitLiteralList(LiteralList literalList) throws ExpressionVisitException, ODataApplicationException; + } diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/LiteralList.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/LiteralList.java new file mode 100644 index 0000000000..9a83d86543 --- /dev/null +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/LiteralList.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.api.uri.queryoption.expression; + +import org.apache.olingo.commons.api.edm.EdmType; + +import java.util.List; + +/** + * Represents a literal expression node in the expression tree + * Literal is not validated by default + * + * E.g. for + * $filter=style_code in ('AB','CD') + * $filter=style_value in (123,345) + */ +public interface LiteralList extends Expression { + + /** + * @return Literal + */ + public List getText(); + + /** + * Numeric literals without an dot and without an e return the smallest possible Edm Integer type. + * 没有点且没有e的数字文字返回尽可能小的Edm Integer类型。 + * Numeric literals without an dot, without an e and larger than 2^63 - 1 are considered as Edm.Decimal + * 没有点,没有e且大于2 ^ 63-1的数字文字被认为是Edm.Decimal + * Numeric literals with an e, are considered to be Edm.Double + * 带有e的数字文字被认为是Edm.Double + * Numeric literals with an dot and without an e, are supposed to be Edm.Decimal + * 带有点而没有e的数字文字应该是 Edm.Decimal + * + * @return Type of the literal if detected. The type of the literal is guessed by the parser. + */ + public EdmType getType(); + +} diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java index 4f12882822..0b2eeabb18 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java @@ -34,14 +34,7 @@ import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.UriResourceSingleton; -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.ExpressionVisitor; -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.MethodKind; -import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind; +import org.apache.olingo.server.api.uri.queryoption.expression.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -70,6 +63,7 @@ public class ExpressionJsonVisitor implements ExpressionVisitor { private static final String MEMBER_NAME = "member"; private static final String VALUE_NAME = "value"; private static final String LITERAL_NAME = "literal"; + private static final String LITERAL_LIST_NAME = "literals"; private static final String EXPRESSION_NAME = "expression"; private static final String LAMBDA_VARIABLE_NAME = "lambdaVariable"; private static final String LAMBDA_FUNCTION_NAME = "lambdaFunction"; @@ -251,6 +245,14 @@ public JsonNode visitEnum(final EdmEnumType type, final List enumValues) return result; } + @Override + public JsonNode visitLiteralList(LiteralList literalList) throws ExpressionVisitException, ODataApplicationException { + return nodeFactory.objectNode() + .put(NODE_TYPE_NAME, LITERAL_LIST_NAME) + .put(TYPE_NAME, getTypeString(literalList.getType())) + .put(VALUE_NAME, literalList.getText().toString()); + } + private String getType(final UnaryOperatorKind operator) { switch (operator) { case MINUS: 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 ed0af1c94d..95e76bfbd4 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 @@ -18,83 +18,32 @@ */ package org.apache.olingo.server.core.uri.parser; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; - -import org.apache.olingo.commons.api.edm.Edm; -import org.apache.olingo.commons.api.edm.EdmComplexType; -import org.apache.olingo.commons.api.edm.EdmElement; -import org.apache.olingo.commons.api.edm.EdmEntitySet; -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.EdmNavigationProperty; -import org.apache.olingo.commons.api.edm.EdmPrimitiveType; -import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; -import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; -import org.apache.olingo.commons.api.edm.EdmProperty; -import org.apache.olingo.commons.api.edm.EdmReturnType; -import org.apache.olingo.commons.api.edm.EdmSingleton; -import org.apache.olingo.commons.api.edm.EdmStructuredType; -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.*; import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; import org.apache.olingo.server.api.OData; -import org.apache.olingo.server.api.uri.UriParameter; -import org.apache.olingo.server.api.uri.UriResourceFunction; -import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; -import org.apache.olingo.server.api.uri.UriResourceNavigation; -import org.apache.olingo.server.api.uri.UriResourcePartTyped; +import org.apache.olingo.server.api.uri.*; import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; -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.UriInfoImpl; -import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl; -import org.apache.olingo.server.core.uri.UriResourceCountImpl; -import org.apache.olingo.server.core.uri.UriResourceEntitySetImpl; -import org.apache.olingo.server.core.uri.UriResourceFunctionImpl; -import org.apache.olingo.server.core.uri.UriResourceItImpl; -import org.apache.olingo.server.core.uri.UriResourceLambdaAllImpl; -import org.apache.olingo.server.core.uri.UriResourceLambdaAnyImpl; -import org.apache.olingo.server.core.uri.UriResourceLambdaVarImpl; -import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl; -import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl; -import org.apache.olingo.server.core.uri.UriResourceRootImpl; -import org.apache.olingo.server.core.uri.UriResourceSingletonImpl; -import org.apache.olingo.server.core.uri.UriResourceStartingTypeFilterImpl; -import org.apache.olingo.server.core.uri.UriResourceTypedImpl; -import org.apache.olingo.server.core.uri.UriResourceWithKeysImpl; +import org.apache.olingo.server.api.uri.queryoption.expression.*; +import org.apache.olingo.server.core.uri.*; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; -import org.apache.olingo.server.core.uri.queryoption.expression.AliasImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.BinaryImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.EnumerationImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.LiteralImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.TypeLiteralImpl; -import org.apache.olingo.server.core.uri.queryoption.expression.UnaryImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.*; import org.apache.olingo.server.core.uri.validator.UriValidationException; +import java.util.*; + public class ExpressionParser { + + public static final List NUMBERSEDMTYPESQFN = + Arrays.asList(EdmPrimitiveTypeKind.SByte.getFullQualifiedName(), + EdmPrimitiveTypeKind.Int16.getFullQualifiedName(), + EdmPrimitiveTypeKind.Int32.getFullQualifiedName(), + EdmPrimitiveTypeKind.Int64.getFullQualifiedName(), + EdmPrimitiveTypeKind.Decimal.getFullQualifiedName(), + EdmPrimitiveTypeKind.Single.getFullQualifiedName(), + EdmPrimitiveTypeKind.Double.getFullQualifiedName() + ); + private static final Map tokenToBinaryOperator; static { Map temp = new EnumMap(TokenKind.class); @@ -115,6 +64,7 @@ public class ExpressionParser { temp.put(TokenKind.MulOperator, BinaryOperatorKind.MUL); temp.put(TokenKind.DivOperator, BinaryOperatorKind.DIV); temp.put(TokenKind.ModOperator, BinaryOperatorKind.MOD); + temp.put(TokenKind.InOperator, BinaryOperatorKind.IN); tokenToBinaryOperator = Collections.unmodifiableMap(temp); } @@ -224,6 +174,16 @@ private Expression parseExprEquality() throws UriParserException, UriValidationE odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.EqualsOperator, TokenKind.NotEqualsOperator); } + + TokenKind operatorTokenKindIn = ParserHelper.next(tokenizer, TokenKind.InOperator); + // Null for everything other than EQ or NE + while (operatorTokenKindIn != null) { + final Expression right = parseExprMultiValues(); + //checkInTypes(left, right); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKindIn), right, + odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean)); + operatorTokenKindIn = ParserHelper.next(tokenizer, TokenKind.InOperator); + } return left; } @@ -313,7 +273,7 @@ private Expression parseExprUnary() throws UriParserException, UriValidationExce } return new UnaryImpl(UnaryOperatorKind.MINUS, expression, getType(expression)); } else if (tokenizer.next(TokenKind.NotOperator)) { - final Expression expression = parseExprValue(); + final Expression expression = parseExprPrimary(); checkType(expression, EdmPrimitiveTypeKind.Boolean); checkNoCollection(expression); return new UnaryImpl(UnaryOperatorKind.NOT, expression, getType(expression)); @@ -390,6 +350,63 @@ private Expression parseExprValue() throws UriParserException, UriValidationExce throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX); } + private Expression parseExprMultiValues() throws UriParserException, UriValidationException { + final LiteralListImpl literalList = new LiteralListImpl(); + if (tokenizer.next(TokenKind.OPEN)) { + while (!tokenizer.next(TokenKind.CLOSE)) { + ParserHelper.bws(tokenizer); + if (tokenizer.next(TokenKind.COMMA)) { + ParserHelper.bws(tokenizer); + } + final Literal literal = (Literal) parseExprValue(); + if (literalList.getType() == null) { + literalList.setType(literal.getType()); + } + + EdmType compatibleNumberEdmType = null; + + if (literal.getType() == null || literalList.getType() == null) { + throw new UriParserSemanticException("incompatible type filter", + UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER); + } else if (NUMBERSEDMTYPESQFN.contains(literalList.getType().getFullQualifiedName()) + || NUMBERSEDMTYPESQFN.contains(literal.getType().getFullQualifiedName())) { + compatibleNumberEdmType = compatibleEdmTypeForNumbers(literal.getType(), literalList.getType()); + } + + if (literal.getType().equals(literalList.getType())) { + literalList.getText().add(literal.getText()); + } else if (compatibleNumberEdmType != null) { + literalList.getText().add(literal.getText()); + literalList.setType(compatibleNumberEdmType); + } else { + literal.getType().getFullQualifiedName(); + throw new UriParserSemanticException("Inconsistent Type -> " + literal.getType().toString() + " != " + + literalList.getType().toString(), + UriParserSemanticException.MessageKeys.INCONSISTENT_VALUE_TYPE_AFTER_IN_SENTENCE, + literal.getType().toString(), literalList.getType().toString()); + } + //ParserHelper.bws(tokenizer); + } + //ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + return literalList; + } + + throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX); + } + + private EdmType compatibleEdmTypeForNumbers(EdmType... edmTypes) { + int max = -1; + EdmType retEdmType = null; + for (EdmType edmType : edmTypes) { + int of = NUMBERSEDMTYPESQFN.indexOf(edmType.getFullQualifiedName()); + if (of > max) { + max = of; + retEdmType = edmType; + } + } + return retEdmType; + } + private Expression parseMethod(final TokenKind nextMethod) throws UriParserException, UriValidationException { // The method token text includes the opening parenthesis so that method calls can be recognized unambiguously. // OData identifiers have to be considered after that. @@ -1104,6 +1121,8 @@ protected static EdmType getType(final Expression expression) throws UriParserEx type = ((Literal) expression).getType(); } else if (expression instanceof TypeLiteral) { type = ((TypeLiteral) expression).getType(); + } else if (expression instanceof LiteralList) { + type = ((LiteralList) expression).getType(); } else if (expression instanceof Enumeration) { type = ((Enumeration) expression).getType(); } else if (expression instanceof Member) { @@ -1201,6 +1220,15 @@ && isType(rightType, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte)) { } } + private void checkInTypes(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; + } + } + private EdmEnumType getEnumType(final String primitiveValueLiteral) throws UriParserException { final String enumTypeName = primitiveValueLiteral.substring(0, primitiveValueLiteral.indexOf('\'')); final EdmEnumType type = edm.getEnumType(new FullQualifiedName(enumTypeName)); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java index ab24a4f173..de23bedf33 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java @@ -77,7 +77,9 @@ public static enum MessageKeys implements MessageKey { FUNCTION_MUST_USE_COLLECTIONS, COLLECTION_NOT_ALLOWED, /** parameter: not implemented part for system query option $id */ - NOT_IMPLEMENTED_SYSTEM_QUERY_OPTION; + NOT_IMPLEMENTED_SYSTEM_QUERY_OPTION, + /** IN 字句的值类型不匹配 */ + INCONSISTENT_VALUE_TYPE_AFTER_IN_SENTENCE; @Override public String getKey() { 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 2363254117..5fd8771b69 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 @@ -121,6 +121,7 @@ public enum TokenKind { AndOperator, EqualsOperator, NotEqualsOperator, + InOperator, GreaterThanOperator, GreaterThanOrEqualsOperator, LessThanOperator, @@ -492,6 +493,9 @@ public boolean next(final TokenKind allowedTokenKind) { case NotEqualsOperator: found = nextBinaryOperator("ne"); break; + case InOperator: + found = nextBinaryOperator("in"); + break; case GreaterThanOperator: found = nextBinaryOperator("gt"); break; @@ -945,7 +949,7 @@ private boolean nextIntegerValue(final boolean signed) { /** * Moves past a decimal value with a fractional part if found; otherwise leaves the index unchanged. - * Whole numbers must be found with {@link #nextIntegerValue()}. + * Whole numbers must be found with {@link #nextIntegerValue(boolean signed)}. */ private boolean nextDecimalValue() { final int lastGoodIndex = index; @@ -961,7 +965,7 @@ private boolean nextDecimalValue() { * Moves past a floating-point-number value with an exponential part * or one of the special constants "NaN", "-INF", and "INF" * if found; otherwise leaves the index unchanged. - * Whole numbers must be found with {@link #nextIntegerValue()}. + * Whole numbers must be found with {@link #nextIntegerValue(boolean signed)}. * Decimal numbers must be found with {@link #nextDecimalValue()}. */ private boolean nextDoubleValue() { @@ -1253,7 +1257,7 @@ private boolean nextLineString() { private boolean nextGeoLineString(final boolean isGeography) { return nextGeoPrefix(isGeography) && nextCharacter('\'') - && nextSrid() && nextCharacter(';') && nextLineString() + && nextSrid() && nextCharacter(';') && nextLineString() && nextCharacter('\''); } @@ -1288,7 +1292,7 @@ private boolean nextPolygon() { private boolean nextGeoPolygon(final boolean isGeography) { return nextGeoPrefix(isGeography) && nextCharacter('\'') - && nextSrid() && nextCharacter(';') && nextPolygon() + && nextSrid() && nextCharacter(';') && nextPolygon() && nextCharacter('\''); } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralListImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralListImpl.java new file mode 100644 index 0000000000..eeec579d08 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralListImpl.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.core.uri.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.ExpressionVisitException; +import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor; +import org.apache.olingo.server.api.uri.queryoption.expression.LiteralList; + +import java.util.ArrayList; +import java.util.List; + +public class LiteralListImpl implements LiteralList { + + private List text; + // type of the element. + private EdmType type; + + public LiteralListImpl(){ + this.text = new ArrayList(); + } + + public LiteralListImpl(final List text, final EdmType type) { + this.text = text; + this.type = type; + } + + public LiteralListImpl setText(List text) { + this.text = text; + return this; + } + + public LiteralListImpl setType(EdmType type) { + this.type = type; + return this; + } + + @Override + public List getText() { + return text; + } + + @Override + public EdmType getType() { + return type; + } + + @Override + public T accept(final ExpressionVisitor visitor) throws ExpressionVisitException, ODataApplicationException { + return visitor.visitLiteralList(this); + } + + @Override + public String toString() { + return type == null ? "NULL" : + type.getFullQualifiedName().getFullQualifiedNameAsString() + text; + } +} diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties index 964cf35edf..564f83b9f4 100644 --- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties +++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties @@ -80,6 +80,7 @@ UriParserSemanticException.ONLY_FOR_PRIMITIVE_TYPES='%1$s' is only allowed for p UriParserSemanticException.FUNCTION_MUST_USE_COLLECTIONS=Only bound functions with collections of structural types as binding parameter and as return type are allowed; '%1$s' is not such a function. UriParserSemanticException.COLLECTION_NOT_ALLOWED=A collection expression is not allowed. UriParserSemanticException.NOT_IMPLEMENTED_SYSTEM_QUERY_OPTION=$id with absolute url different from base url is not implemented! +UriParserSemanticException.INCONSISTENT_VALUE_TYPE_AFTER_IN_SENTENCE=Inconsistent Type behind the IN statement! '%1$s' != '%2$s' UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported. UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported. diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/LexerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/LexerTest.java index df3b506527..261eaf0f3c 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/LexerTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/LexerTest.java @@ -158,6 +158,42 @@ public void queryExpressions() { test.run("$filter=concat(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.ConcatMethod).isText("concat("); } + @Test + public void testIn() { + + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier) + .isText("name"); + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier, TokenKind.InOperator) + .isText(" in "); + + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier, TokenKind.InOperator, TokenKind.OPEN) + .isText("("); + + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier, TokenKind.InOperator, TokenKind.OPEN, + TokenKind.StringValue) + .isText("'Lyndon'"); + + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier, TokenKind.InOperator, TokenKind.OPEN, + TokenKind.StringValue, TokenKind.COMMA) + .isText(","); + + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier, TokenKind.InOperator, TokenKind.OPEN, + TokenKind.StringValue, TokenKind.COMMA, TokenKind.StringValue) + .isText("'Mike'"); + + test.run("name in ('Lyndon','Mike')") + .has(TokenKind.ODataIdentifier, TokenKind.InOperator, TokenKind.OPEN, + TokenKind.StringValue, TokenKind.COMMA, TokenKind.StringValue, + TokenKind.CLOSE) + .isText(")"); + } + @Test public void literalDataValues() { // null 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 e47f6c8e51..3ad7564517 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 @@ -717,6 +717,13 @@ public void aggregation() { assertTrue(new UriTokenizer("isdefined(x)").next(TokenKind.IsDefinedMethod)); } + @Test + public void hasIn() { + final UriTokenizer tokenizer = new UriTokenizer(" in "); + boolean hasIn = tokenizer.next(TokenKind.InOperator); + System.out.println(hasIn); + } + private void wrongToken(final TokenKind kind, final String value, final char disturbCharacter) { assertFalse(new UriTokenizer(disturbCharacter + value).next(kind)); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java index 253dbde308..c8bf31210d 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java @@ -44,15 +44,7 @@ import org.apache.olingo.server.api.uri.UriResourceLambdaAny; import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; import org.apache.olingo.server.api.uri.UriResourceProperty; -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.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.Literal; -import org.apache.olingo.server.api.uri.queryoption.expression.Member; -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.api.uri.queryoption.expression.*; import org.apache.olingo.server.core.uri.UriResourceLambdaVarImpl; import org.apache.olingo.server.tecsvc.data.DataProvider; import org.apache.olingo.server.tecsvc.processor.queryoptions.expression.operand.TypedOperand; @@ -318,6 +310,12 @@ public VisitorOperand visitEnum(final EdmEnumType type, final List enumV return new TypedOperand(result, type); } + @Override + public VisitorOperand visitLiteralList(LiteralList literalList) + throws ExpressionVisitException, ODataApplicationException { + return null; + } + private VisitorOperand throwNotImplemented() throws ODataApplicationException { throw new ODataApplicationException("Not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/UriParserTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/UriParserTest.java index c97cb93361..cae3a5591c 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/UriParserTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/UriParserTest.java @@ -20,14 +20,20 @@ import java.util.Arrays; import java.util.Collections; +import java.util.List; import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edmx.EdmxReference; import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.core.edm.primitivetype.*; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; import org.apache.olingo.server.core.uri.parser.UriParserSemanticException.MessageKeys; +import org.apache.olingo.server.core.uri.testutil.FilterTreeToText; import org.apache.olingo.server.core.uri.testutil.FilterValidator; import org.apache.olingo.server.core.uri.testutil.ResourceValidator; import org.apache.olingo.server.core.uri.testutil.TestUriValidator; @@ -910,6 +916,77 @@ public void trimQueryOptionsValue() throws Exception { .isKind(UriInfoKind.resource).goFilter().isBinary(BinaryOperatorKind.EQ).is("< eq <12>>"); } + @Test + public void customInOptionNumber() throws Exception { + FilterValidator esAllPrim = testUri.run("ESAllPrim", "$filter= PropertyInt16 in (12,10) ") + .isKind(UriInfoKind.resource).goFilter().isBinary(BinaryOperatorKind.IN); + String expression = FilterTreeToText.Serialize(esAllPrim.getFilter()); + System.out.println(expression); + } + + @Test + public void customInOptionHybridNumber() throws Exception { + FilterValidator esAllPrim = testUri.run("ESAllPrim", "$filter=PropertyInt16 in (2, 10.5) ") + .isKind(UriInfoKind.resource).goFilter().isBinary(BinaryOperatorKind.IN); + String expression = FilterTreeToText.Serialize(esAllPrim.getFilter()); + System.out.println(expression); + } + + @Test + public void customInOptionString() throws Exception { + FilterValidator esAllPrim = testUri.run("ESAllPrim", "$filter=PropertyString in ('A', 'B') ") + .isKind(UriInfoKind.resource).goFilter().isBinary(BinaryOperatorKind.IN); + String expression = FilterTreeToText.Serialize(esAllPrim.getFilter()); + System.out.println(expression); + } + + @Test + public void autoFloatHybirdNumber() { + EdmType edmType = null; + try { + edmType = compatibleEdmTypeForNumbers(new EdmString(), new EdmDouble(), + new EdmInt16(), new EdmDecimal(), new EdmSingle()); + } catch (UriParserSemanticException e) { + e.printStackTrace(); + } + System.out.println(edmType); + } + + + private EdmType compatibleEdmTypeForNumbers(EdmType... edmTypes) throws UriParserSemanticException { + final List numbersEdmTypesQFN = + Arrays.asList(EdmPrimitiveTypeKind.SByte.getFullQualifiedName(), + EdmPrimitiveTypeKind.Int16.getFullQualifiedName(), + EdmPrimitiveTypeKind.Int32.getFullQualifiedName(), + EdmPrimitiveTypeKind.Int64.getFullQualifiedName(), + EdmPrimitiveTypeKind.Decimal.getFullQualifiedName(), + EdmPrimitiveTypeKind.Single.getFullQualifiedName(), + EdmPrimitiveTypeKind.Double.getFullQualifiedName() + ); + + int max = -1; + EdmType retEdmType = null; + for (EdmType edmType : edmTypes) { + int of = numbersEdmTypesQFN.indexOf(edmType.getFullQualifiedName()); + if (of > max) { + max = of; + retEdmType = edmType; + } + if (retEdmType == null) { + throw new UriParserSemanticException("incompatible type filter", MessageKeys.INCOMPATIBLE_TYPE_FILTER); + } + } + + FullQualifiedName fullQualifiedName = numbersEdmTypesQFN.get(max); + System.out.println(fullQualifiedName); + + EdmPrimitiveTypeKind edmPrimitiveTypeKind = EdmPrimitiveTypeKind.valueOfFQN(fullQualifiedName); + System.out.println(edmPrimitiveTypeKind); + + System.out.println(retEdmType.getFullQualifiedName()); + return retEdmType; + } + @Test public void customQueryOption() throws Exception { testUri.run("ESTwoKeyNav", "custom") diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterTreeToText.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterTreeToText.java index 2103dd1603..e057aa49b1 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterTreeToText.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterTreeToText.java @@ -28,14 +28,7 @@ import org.apache.olingo.server.api.uri.UriResourceLambdaAny; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.queryoption.FilterOption; -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.ExpressionVisitor; -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.MethodKind; -import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind; +import org.apache.olingo.server.api.uri.queryoption.expression.*; public class FilterTreeToText implements ExpressionVisitor { @@ -150,4 +143,28 @@ public String visitEnum(final EdmEnumType type, final List enumValues) return "<" + type.getFullQualifiedName().getFullQualifiedNameAsString() + "<" + tmp + ">>"; } + @Override + public String visitLiteralList(LiteralList literalList) throws ExpressionVisitException, ODataApplicationException { + List texts = literalList.getText(); + if (texts != null) { + StringBuffer sb = new StringBuffer(); + + int eleNums = texts.size(); + + sb.append("<("); + for (int i = 0; i < texts.size(); i++) { + String comma = ","; + if (i == eleNums-1) { + comma = ""; + } + sb.append(texts.get(i)); + sb.append(comma); + } + sb.append(")>"); + + return sb.toString(); + } + + return null; + } } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java index 246a408c3d..4e4eb9f17e 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java @@ -68,6 +68,10 @@ public class FilterValidator implements TestValidator { private Expression curExpression; private Expression rootExpression; + public FilterOption getFilter() { + return filter; + } + // --- Setup --- public FilterValidator setValidator(final TestValidator uriValidator) { invokedByValidator = uriValidator;