From 77efb0b666003881d5d264234c63539e6a558fb4 Mon Sep 17 00:00:00 2001 From: Chloe Date: Thu, 16 Jan 2020 12:00:41 -0800 Subject: [PATCH] Syntax and semantic exceptions handling for unsupported features (#345) * Added a filter in semantic layer for unsupported features: nested functions, aggregations with function aggregators, functions with aggregation arguments * Excluded the special esfunctions that support nested * Added unit tests * Added integ tests * Added COUNT in function-nested aggregates; added error msg assertions to the tests; corrected explanation for tests to ignore --- src/main/antlr/OpenDistroSqlParser.g4 | 68 ++++++++++- .../visitor/AntlrSqlParseTreeVisitor.java | 24 +++- .../visitor/UnsupportedSemanticVerifier.java | 108 ++++++++++++++++++ .../SemanticAnalyzerOperatorTest.java | 4 +- .../SemanticAnalyzerScalarFunctionTest.java | 7 ++ .../visitor/AntlrSqlParseTreeVisitorTest.java | 54 ++++++++- .../sql/esintgtest/QueryAnalysisIT.java | 60 ++++++++-- 7 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/UnsupportedSemanticVerifier.java diff --git a/src/main/antlr/OpenDistroSqlParser.g4 b/src/main/antlr/OpenDistroSqlParser.g4 index c35140e6c0..ce30892e16 100644 --- a/src/main/antlr/OpenDistroSqlParser.g4 +++ b/src/main/antlr/OpenDistroSqlParser.g4 @@ -285,13 +285,21 @@ expressions : expression (',' expression)* ; +aggregateFunction + : functionAsAggregatorFunction #functionAsAggregatorFunctionCall + | aggregateWindowedFunction #aggregateWindowedFunctionCall + ; -// Functions +scalarFunction + : scalarFunctionName '(' nestedFunctionArgs+ ')' #nestedFunctionCall + | scalarFunctionName '(' functionArgs? ')' #scalarFunctionCall + ; functionCall - : specificFunction #specificFunctionCall - | aggregateWindowedFunction #aggregateFunctionCall - | scalarFunctionName '(' functionArgs? ')' #scalarFunctionCall + : aggregateFunction #aggregateFunctionCall + | scalarFunctionName '(' aggregateWindowedFunction ')' #aggregationAsArgFunctionCall + | scalarFunction #scalarFunctionsCall + | specificFunction #specificFunctionCall | fullId '(' functionArgs? ')' #udfFunctionCall ; @@ -324,10 +332,30 @@ aggregateWindowedFunction | COUNT '(' aggregator=DISTINCT functionArgs ')' ; +functionAsAggregatorFunction + : (AVG | MAX | MIN | SUM) + '(' aggregator=(ALL | DISTINCT)? functionCall ')' + | COUNT '(' aggregator=(ALL | DISTINCT)? functionCall ')' + ; + scalarFunctionName : functionNameBase ; +/* +Separated aggregate to function-aggregator and nonfunction-aggregator aggregations. + +Current related rules: aggregateWindowedFunction, functionAsAggregatorFunction, aggregateFunction, functionCall +Original rules: functionCall (as is shown in below changes), no aggregateWindowFunction, no functionAsAggregatorFunction, + no aggregateFunction + +==== + +Separated function argument rule to nonFunctionCall and functionCall +functions with functionCall arguments are taken as nested functions + +Current related rules: functionArgs, functionArg, nestedFunctionArgs +Original rules: functionArgs : (constant | fullColumnName | functionCall | expression) ( @@ -340,6 +368,36 @@ functionArg : constant | fullColumnName | functionCall | expression ; +==== + +Accordingly functionCall rule is changed by separating scalar functions +to nested functions and non-nested functons. +Current related rules: functionCall, scalarFunction +Original rule: +functionCall + : specificFunction #specificFunctionCall + | aggregateWindowedFunction #aggregateFunctionCall + | scalarFunctionName '(' functionArgs? ')' #scalarFunctionCall + | fullId '(' functionArgs? ')' #udfFunctionCall + ; +*/ + +functionArgs + : (constant | fullColumnName | expression) + ( + ',' + (constant | fullColumnName | expression) + )* + ; + +functionArg + : constant | fullColumnName | expression + ; + +nestedFunctionArgs + : functionCall (',' functionArgs)? + ; + // Expressions, predicates @@ -414,7 +472,7 @@ functionNameBase | LOG10 | LOG2 | LOWER | LTRIM | MAKETIME | MODULUS | MONTH | MONTHNAME | MULTIPLY | NOW | PI | POW | POWER | RADIANS | RAND | REPLACE | RIGHT | RINT | ROUND | RTRIM | SIGN | SIGNUM | SIN | SINH | SQRT | SUBSTRING | SUBTRACT | TAN | TIMESTAMP | TRIM - | UPPER | YEAR + | UPPER | YEAR | ADDDATE | ADDTIME | GREATEST | LEAST ; esFunctionNameBase diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitor.java b/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitor.java index ba359d2d0a..0a619c172e 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitor.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitor.java @@ -30,7 +30,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; - +import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.AggregationAsArgFunctionCallContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.AggregateWindowedFunctionContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.AtomTableItemContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.BinaryComparisonPredicateContext; @@ -43,9 +43,11 @@ import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.FunctionNameBaseContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.InPredicateContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.IsExpressionContext; +import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.MathOperatorContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.MinusSelectContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.OuterJoinContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.PredicateContext; +import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.RegexpPredicateContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.RootContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.ScalarFunctionCallContext; import static com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.SelectElementsContext; @@ -217,13 +219,25 @@ public T visitUdfFunctionCall(UdfFunctionCallContext ctx) { return reduce(func, ctx.functionArgs()); } - // This check should be able to accomplish in grammar @Override public T visitScalarFunctionCall(ScalarFunctionCallContext ctx) { + UnsupportedSemanticVerifier.verify(ctx); T func = visit(ctx.scalarFunctionName()); return reduce(func, ctx.functionArgs()); } + @Override + public T visitMathOperator(MathOperatorContext ctx) { + UnsupportedSemanticVerifier.verify(ctx); + return super.visitMathOperator(ctx); + } + + @Override + public T visitRegexpPredicate(RegexpPredicateContext ctx) { + UnsupportedSemanticVerifier.verify(ctx); + return super.visitRegexpPredicate(ctx); + } + @Override public T visitSelectElements(SelectElementsContext ctx) { return visitor.visitSelect(ctx.selectElement(). @@ -247,6 +261,12 @@ public T visitSelectExpressionElement(SelectExpressionElementContext ctx) { return visitSelectItem(ctx.expression(), ctx.uid()); } + @Override + public T visitAggregationAsArgFunctionCall(AggregationAsArgFunctionCallContext ctx) { + UnsupportedSemanticVerifier.verify(ctx); + return super.visitAggregationAsArgFunctionCall(ctx); + } + @Override public T visitAggregateWindowedFunction(AggregateWindowedFunctionContext ctx) { String funcName = ctx.getChild(0).getText(); diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/UnsupportedSemanticVerifier.java b/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/UnsupportedSemanticVerifier.java new file mode 100644 index 0000000000..d673b6cbcd --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/UnsupportedSemanticVerifier.java @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.antlr.visitor; + +import com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser; +import com.amazon.opendistroforelasticsearch.sql.exception.SqlFeatureNotImplementedException; +import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; +import com.google.common.collect.Sets; + +import java.util.Set; + +import com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.AggregationAsArgFunctionCallContext; +import com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.MathOperatorContext; +import com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.RegexpPredicateContext; +import com.amazon.opendistroforelasticsearch.sql.antlr.parser.OpenDistroSqlParser.ScalarFunctionCallContext; + +public class UnsupportedSemanticVerifier { + + private static final Set mathConstants = Sets.newHashSet( + "e", "pi" + ); + + private static final Set supportedNestedFunctions = Sets.newHashSet( + "nested", "reverse_nested", "score", "match_query", "matchquery" + ); + + /** + * The following two sets include the functions and operators that have requested or issued by users + * but the plugin does not support yet. + */ + private static final Set unsupportedFunctions = Sets.newHashSet( + "adddate", "addtime", "datetime", "greatest", "least" + ); + + private static final Set unsupportedOperators = Sets.newHashSet( + "div" + ); + + + /** + * The scalar function calls are separated into (a)typical function calls; (b)nested function calls with functions + * as arguments, like abs(log(...)); (c)aggregations with functions as aggregators, like max(abs(....)). + * Currently, we do not support nested functions or nested aggregations, aka (b) and (c). + * However, for the special EsFunctions included in the [supportedNestedFunctions] set, we have supported them in + * nested function calls and aggregations (b&c). Besides, the math constants included in the [mathConstants] set + * are regraded as scalar functions, but they are working well in the painless script. + * + * Thus, the types of functions to throw exceptions: + * (I)case (b) except that the arguments are from the [mathConstants] set; + * (II) case (b) except that the arguments are from the [supportedNestedFunctions] set; + * (III) case (c) except that the aggregators are from thet [supportedNestedFunctions] set. + */ + public static void verify(ScalarFunctionCallContext ctx) { + String funcName = StringUtils.toLower(ctx.scalarFunctionName().getText()); + + // type (III) + if (ctx.parent.parent instanceof OpenDistroSqlParser.FunctionAsAggregatorFunctionContext + && !(supportedNestedFunctions.contains(StringUtils.toLower(funcName)))) { + throw new SqlFeatureNotImplementedException(StringUtils.format( + "Aggregation calls with function aggregator like [%s] are not supported yet", + ctx.parent.parent.getText())); + + // type (I) and (II) + } else if (ctx.parent.parent instanceof OpenDistroSqlParser.NestedFunctionArgsContext + && !(mathConstants.contains(funcName) || supportedNestedFunctions.contains(funcName))) { + throw new SqlFeatureNotImplementedException(StringUtils.format( + "Nested function calls like [%s] are not supported yet", ctx.parent.parent.parent.getText())); + + // unsupported functions + } else if (unsupportedFunctions.contains(funcName)) { + throw new SqlFeatureNotImplementedException(StringUtils.format("Function [%s] is not supported yet", + funcName)); + } + } + + /** + * For functions with aggregation arguments, like abs(max(...)); + * Temporarily added since this type of functions is under fixing. + */ + public static void verify(AggregationAsArgFunctionCallContext ctx) { + throw new SqlFeatureNotImplementedException(StringUtils.format( + "Nested function calls with aggregation argument like [%s] are not supported yet", ctx.getText())); + } + + public static void verify(MathOperatorContext ctx) { + if (unsupportedOperators.contains(StringUtils.toLower(ctx.getText()))) { + throw new SqlFeatureNotImplementedException(StringUtils.format("Operator [%s] is not supported yet", + ctx.getText())); + } + } + + public static void verify(RegexpPredicateContext ctx) { + throw new SqlFeatureNotImplementedException("Regexp predicate is not supported yet"); + } +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerOperatorTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerOperatorTest.java index 861217cc76..74c13613cb 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerOperatorTest.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerOperatorTest.java @@ -55,9 +55,9 @@ public void compareSubstringFunctionCallEqualsToNumberShouldFail() { } @Test - public void compareLogAndAbsFunctionCallWithIntegerSmallerThanStringShouldFail() { + public void compareLogFunctionCallWithIntegerSmallerThanStringShouldFail() { expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE LOG(ABS(age)) < 'test'", + "SELECT * FROM semantics WHERE LOG(age) < 'test'", "Operator [<] cannot work with [DOUBLE, STRING]." ); } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java index efb9ba82b7..d23f14d220 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java @@ -15,6 +15,7 @@ package com.amazon.opendistroforelasticsearch.sql.antlr.semantic; +import org.junit.Ignore; import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.types.base.ESDataType; import org.junit.Test; @@ -95,6 +96,7 @@ public void logFunctionCallWithUnknownFieldShouldPass() { validate("SELECT LOG(new_field) FROM semantics"); } + @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") @Test public void substringWithLogFunctionCallWithUnknownFieldShouldPass() { expectValidationFailWithErrorMessages( @@ -104,21 +106,25 @@ public void substringWithLogFunctionCallWithUnknownFieldShouldPass() { ); } + @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") @Test public void logFunctionCallWithResultOfAbsFunctionCallWithOneNumberShouldPass() { validate("SELECT LOG(ABS(age)) FROM semantics"); } + @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") @Test public void logFunctionCallWithMoreNestedFunctionCallWithOneNumberShouldPass() { validate("SELECT LOG(ABS(SQRT(balance))) FROM semantics"); } + @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") @Test public void substringFunctionCallWithResultOfAnotherSubstringAndAbsFunctionCallShouldPass() { validate("SELECT SUBSTRING(SUBSTRING(city, ABS(age), 1), 2, ABS(1)) FROM semantics"); } + @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") @Test public void substringFunctionCallWithResultOfMathFunctionCallShouldFail() { expectValidationFailWithErrorMessages( @@ -128,6 +134,7 @@ public void substringFunctionCallWithResultOfMathFunctionCallShouldFail() { ); } + @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") @Test public void logFunctionCallWithResultOfSubstringFunctionCallShouldFail() { expectValidationFailWithErrorMessages( diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitorTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitorTest.java index c85f7b957f..9dc23f7e4c 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitorTest.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/antlr/visitor/AntlrSqlParseTreeVisitorTest.java @@ -21,10 +21,12 @@ import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.types.Type; import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.types.special.Product; import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.visitor.TypeChecker; -import com.amazon.opendistroforelasticsearch.sql.antlr.visitor.AntlrSqlParseTreeVisitor; +import com.amazon.opendistroforelasticsearch.sql.exception.SqlFeatureNotImplementedException; import org.antlr.v4.runtime.tree.ParseTree; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.util.Arrays; @@ -54,6 +56,9 @@ public Type visitFieldName(String fieldName) { } }; + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + @Test public void selectNumberShouldReturnNumberAsQueryVisitingResult() { Type result = visit("SELECT age FROM test"); @@ -74,6 +79,53 @@ public void selectStarShouldReturnEmptyProductAsQueryVisitingResult() { Assert.assertTrue(result.isCompatible(new Product(emptyList()))); } + @Test + public void visitSelectNestedFunctionShouldThrowException() { + exceptionRule.expect(SqlFeatureNotImplementedException.class); + exceptionRule.expectMessage("Nested function calls like [abs(log(age))] are not supported yet"); + visit("SELECT abs(log(age)) FROM test"); + } + + @Test + public void visitWhereNestedFunctionShouldThrowException() { + exceptionRule.expect(SqlFeatureNotImplementedException.class); + exceptionRule.expectMessage("Nested function calls like [abs(log(age))] are not supported yet"); + visit("SELECT age FROM test WHERE abs(log(age)) = 1"); + } + + @Test + public void visitMathConstantAsNestedFunctionShouldPass() { + visit("SELECT abs(pi()) FROM test"); + } + + @Test + public void visitSupportedNestedFunctionShouldPass() { + visit("SELECT sum(nested(name.balance)) FROM test"); + } + + /** Temporarily added, should be deleted after this case is fixed */ + @Test + public void visitSelectNestedAggregationAsFunctionArgShouldThrowException() { + exceptionRule.expect(SqlFeatureNotImplementedException.class); + exceptionRule.expectMessage( + "Nested function calls with aggregation argument like [abs(max(age))] are not supported yet"); + visit("SELECT abs(max(age)) FROM test"); + } + + @Test + public void visitFunctionAsAggregatorShouldThrowException() { + exceptionRule.expect(SqlFeatureNotImplementedException.class); + exceptionRule.expectMessage("Aggregation calls with function aggregator like [max(abs(age))] are not supported yet"); + visit("SELECT max(abs(age)) FROM test"); + } + + @Test + public void visitUnsupportedOperatorShouldThrowException() { + exceptionRule.expect(SqlFeatureNotImplementedException.class); + exceptionRule.expectMessage("Operator [DIV] is not supported yet"); + visit("SELECT balance DIV age FROM test"); + } + private ParseTree createParseTree(String sql) { return new OpenDistroSqlAnalyzer(new SqlAnalysisConfig(true, true, 1000)).analyzeSyntax(sql); } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryAnalysisIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryAnalysisIT.java index 371e833173..389a6688ff 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryAnalysisIT.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryAnalysisIT.java @@ -17,6 +17,7 @@ import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.SemanticAnalysisException; import com.amazon.opendistroforelasticsearch.sql.antlr.syntax.SyntaxAnalysisException; +import com.amazon.opendistroforelasticsearch.sql.exception.SqlFeatureNotImplementedException; import com.amazon.opendistroforelasticsearch.sql.exception.SqlParseException; import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; import org.elasticsearch.action.index.IndexRequest; @@ -38,6 +39,7 @@ import static org.elasticsearch.common.xcontent.XContentType.JSON; import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; import static org.elasticsearch.rest.RestStatus.OK; +import static org.elasticsearch.rest.RestStatus.SERVICE_UNAVAILABLE; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -163,14 +165,6 @@ public void aggregateFunctionCallWithWrongNumberOfArgumentShouldThrowSemanticExc ); } - @Test - public void aggregateFunctionCallWithWrongScalarFunctionCallShouldThrowSemanticException() { - queryShouldThrowSemanticException( - "SELECT MAX(LOG(firstname)) FROM elasticsearch-sql_test_index_bank GROUP BY city", - "Function [LOG] cannot work with [TEXT]." - ); - } - @Test public void compareIntegerFieldWithBooleanShouldThrowSemanticException() { queryShouldThrowSemanticException( @@ -225,6 +219,44 @@ public void useInClauseWithIncompatibleFieldTypesShouldFail() { ); } + @Test + public void queryWithNestedFunctionShouldFail() { + queryShouldThrowFeatureNotImplementedException( + "SELECT abs(log(balance)) FROM elasticsearch-sql_test_index_bank", + "Nested function calls like [abs(log(balance))] are not supported yet" + ); + } + + @Test + public void nestedFunctionWithMathConstantAsInnerFunctionShouldPass() { + queryShouldPassAnalysis("SELECT log(e()) FROM elasticsearch-sql_test_index_bank"); + } + + /** Temporarily added, should be deleted after this case is fixed */ + @Test + public void functionWithAggregatorArgShouldFail() { + queryShouldThrowFeatureNotImplementedException( + "SELECT abs(max(age)) FROM elasticsearch-sql_test_index_bank", + "Nested function calls with aggregation argument like [abs(max(age))] are not supported yet" + ); + } + + @Test + public void aggregateWithFunctionAggregatorShouldFail() { + queryShouldThrowFeatureNotImplementedException( + "SELECT max(log(age)) FROM elasticsearch-sql_test_index_bank", + "Aggregation calls with function aggregator like [max(log(age))] are not supported yet" + ); + } + + @Test + public void queryWithUnsupportedFunctionShouldFail() { + queryShouldThrowFeatureNotImplementedException( + "SELECT balance DIV age FROM elasticsearch-sql_test_index_bank", + "Operator [DIV] is not supported yet" + ); + } + /** Run the query with cluster setting changed and cleaned after complete */ private void runWithClusterSetting(ClusterSetting setting, Runnable query) { try { @@ -254,14 +286,22 @@ private void queryShouldThrowSemanticException(String query, String... expectedM queryShouldThrowException(query, SemanticAnalysisException.class, expectedMsgs); } + private void queryShouldThrowFeatureNotImplementedException(String query, String... expectedMsgs) { + queryShouldThrowExceptionWithRestStatus(query, SqlFeatureNotImplementedException.class, SERVICE_UNAVAILABLE, expectedMsgs); + } + private void queryShouldThrowException(String query, Class exceptionType, String... expectedMsgs) { + queryShouldThrowExceptionWithRestStatus(query, exceptionType, BAD_REQUEST, expectedMsgs); + } + + private void queryShouldThrowExceptionWithRestStatus(String query, Class exceptionType, RestStatus status, String... expectedMsgs) { try { executeQuery(query); Assert.fail("Expected ResponseException, but none was thrown for query: " + query); } catch (ResponseException e) { ResponseAssertion assertion = new ResponseAssertion(e.getResponse()); - assertion.assertStatusEqualTo(BAD_REQUEST.getStatus()); + assertion.assertStatusEqualTo(status.getStatus()); assertion.assertBodyContains("\"type\": \"" + exceptionType.getSimpleName() + "\""); for (String msg : expectedMsgs) { assertion.assertBodyContains(msg); @@ -269,7 +309,7 @@ private void queryShouldThrowException(String query, Class exceptionType, } catch (IOException e) { throw new IllegalStateException( - "Unexpected IOException raised rather than expected AnalysisException for query: " + query); + "Unexpected IOException raised rather than expected AnalysisException for query: " + query); } }