From 842c215dc5c3dab12efa90c2ac8df0146bc52780 Mon Sep 17 00:00:00 2001 From: Chloe Date: Sun, 3 Nov 2019 17:49:25 -0800 Subject: [PATCH] Support CASE statement in one more grammer (#262) * Reassign condition expressions of the items in case express * Added UT and IT * Fixed the JDBC formatter for the case statement * Added IT to test JDBC formatter for the case when statement * Updated the release notes --- opendistro-elasticsearch-sql.release-notes | 7 ++++- .../sql/executor/format/SelectResultSet.java | 3 +++ .../sql/parser/CaseWhenParser.java | 12 +++++++++ .../sql/parser/WhereParser.java | 18 ++++++------- .../sql/esintgtest/QueryIT.java | 26 +++++++++++++++++++ .../sql/unittest/parser/SqlParserTest.java | 18 +++++++++++++ 6 files changed, 74 insertions(+), 10 deletions(-) diff --git a/opendistro-elasticsearch-sql.release-notes b/opendistro-elasticsearch-sql.release-notes index 0a5e8c29c5..2b8d29db9b 100644 --- a/opendistro-elasticsearch-sql.release-notes +++ b/opendistro-elasticsearch-sql.release-notes @@ -5,6 +5,9 @@ * Feature [#258](https://github.com/opendistro-for-elasticsearch/sql/issues/185): Elasticsearch 7.3.2 compatibility * Feature [#201](https://github.com/opendistro-for-elasticsearch/sql/pull/201): Improve query verification by adding semantic analyzer +* Enhancement [#262](https://github.com/opendistro-for-elasticsearch/sql/pull/262): Support CASE statement in one more grammer +* Enhancement [#253](https://github.com/opendistro-for-elasticsearch/sql/pull/253): Support Cast function +* Enhancement [#260](https://github.com/opendistro-for-elasticsearch/sql/pull/260): Support string operators: ASCII, RTRIM, LTRIM, LOCATE, LENGTH, REPLACE * Enhancement [#254](https://github.com/opendistro-for-elasticsearch/sql/issues/254): Support SELECT * Enhancement [#251](https://github.com/opendistro-for-elasticsearch/sql/pull/251): Support number operators: POWER, ATAN2, COT, SIGN/SIGNUM * Enhancement [#215](https://github.com/opendistro-for-elasticsearch/sql/issues215): Support ordinal in GROUP/ORDER BY clause @@ -19,6 +22,7 @@ * Enhancement [#82](https://github.com/opendistro-for-elasticsearch/sql/issues/82): Pre-verification before actual execution of query * Enhancement [#75](https://github.com/opendistro-for-elasticsearch/sql/issues/75): Support order by on SQL functions like SUM etc +* BugFix [#265](https://github.com/opendistro-for-elasticsearch/sql/pull/265): Fix the LOG function that delivered inaccurate result * BugFix [#233](https://github.com/opendistro-for-elasticsearch/sql/issues/233): Function names are case-sensitive * BugFix [#191](https://github.com/opendistro-for-elasticsearch/sql/issues/191): Fix new syntax check for LEFT JOIN on nested field and metadata field * BugFix [#188](https://github.com/opendistro-for-elasticsearch/sql/issues/188): Having doesn't working on nested field @@ -34,9 +38,10 @@ * BugFix [#121](https://github.com/opendistro-for-elasticsearch/sql/issues/121): Dot/period at start of index name fails to parse * BugFix [#111](https://github.com/opendistro-for-elasticsearch/sql/issues/111): JDBC format of aggregation query with date_format adds unnecessary column bug -## 2019-10-15, Version 1.2.1 (Current) +## 2019-10-15, Version 1.2.1 ### Notable changes + * Feature [#202](https://github.com/opendistro-for-elasticsearch/sql/issues/202): Elasticsearch 7.2.1 compatibility ## 2019-07-23, Version 1.2.0 diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/sql/executor/format/SelectResultSet.java b/src/main/java/com/amazon/opendistroforelasticsearch/sql/executor/format/SelectResultSet.java index 954840f602..b0e0054547 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/sql/executor/format/SelectResultSet.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/sql/executor/format/SelectResultSet.java @@ -15,6 +15,7 @@ package com.amazon.opendistroforelasticsearch.sql.executor.format; +import com.alibaba.druid.sql.ast.expr.SQLCaseExpr; import com.alibaba.druid.sql.ast.expr.SQLCastExpr; import com.amazon.opendistroforelasticsearch.sql.domain.Field; import com.amazon.opendistroforelasticsearch.sql.domain.JoinSelect; @@ -326,6 +327,8 @@ private Schema.Type fetchMethodReturnType(Field field) { if (field.getExpression() instanceof SQLCastExpr) { return SQLFunctions.getCastFunctionReturnType( ((SQLCastExpr) field.getExpression()).getDataType().getName()); + } else if (field.getExpression() instanceof SQLCaseExpr) { + return Schema.Type.TEXT; } return SQLFunctions.getScriptFunctionReturnType( ((ScriptMethodField) field).getFunctionName()); diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/CaseWhenParser.java b/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/CaseWhenParser.java index 9b878422f1..4633799d77 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/CaseWhenParser.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/CaseWhenParser.java @@ -16,6 +16,8 @@ package com.amazon.opendistroforelasticsearch.sql.parser; import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; import com.alibaba.druid.sql.ast.expr.SQLCaseExpr; import com.alibaba.druid.sql.ast.expr.SQLNullExpr; import com.amazon.opendistroforelasticsearch.sql.domain.Condition; @@ -45,6 +47,16 @@ public CaseWhenParser(SQLCaseExpr caseExpr, String alias, String tableAlias) { public String parse() throws SqlParseException { List result = new ArrayList<>(); + if (caseExpr.getValueExpr() != null) { + for (SQLCaseExpr.Item item : caseExpr.getItems()) { + SQLExpr left = caseExpr.getValueExpr(); + SQLExpr right = item.getConditionExpr(); + SQLBinaryOpExpr conditionExpr = new SQLBinaryOpExpr(left, SQLBinaryOperator.Equality, right); + item.setConditionExpr(conditionExpr); + } + caseExpr.setValueExpr(null); + } + for (SQLCaseExpr.Item item : caseExpr.getItems()) { SQLExpr conditionExpr = item.getConditionExpr(); diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/WhereParser.java b/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/WhereParser.java index 8f4af81dbf..ea2981da32 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/WhereParser.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/sql/parser/WhereParser.java @@ -102,10 +102,10 @@ public void parseWhere(SQLExpr expr, Where where) throws SqlParseException { if (expr instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr bExpr = (SQLBinaryOpExpr) expr; - if (explanSpecialCondWithBothSidesAreLiterals(bExpr, where)) { + if (explainSpecialCondWithBothSidesAreLiterals(bExpr, where)) { return; } - if (explanSpecialCondWithBothSidesAreProperty(bExpr, where)) { + if (explainSpecialCondWithBothSidesAreProperty(bExpr, where)) { return; } } @@ -118,7 +118,7 @@ public void parseWhere(SQLExpr expr, Where where) throws SqlParseException { parseWhere(((SQLNotExpr) expr).getExpr(), where); negateWhere(where); } else { - explanCond("AND", expr, where); + explainCond("AND", expr, where); } } @@ -135,7 +135,7 @@ private void negateWhere(Where where) throws SqlParseException { } //some where conditions eg. 1=1 or 3>2 or 'a'='b' - private boolean explanSpecialCondWithBothSidesAreLiterals(SQLBinaryOpExpr bExpr, Where where) + private boolean explainSpecialCondWithBothSidesAreLiterals(SQLBinaryOpExpr bExpr, Where where) throws SqlParseException { if ((bExpr.getLeft() instanceof SQLNumericLiteralExpr || bExpr.getLeft() instanceof SQLCharExpr) && (bExpr.getRight() instanceof SQLNumericLiteralExpr || bExpr.getRight() instanceof SQLCharExpr) @@ -150,14 +150,14 @@ private boolean explanSpecialCondWithBothSidesAreLiterals(SQLBinaryOpExpr bExpr, + " " + operator + " " + Util.expr2Object(bExpr.getRight(), "'")) ); - explanCond("AND", sqlMethodInvokeExpr, where); + explainCond("AND", sqlMethodInvokeExpr, where); return true; } return false; } //some where conditions eg. field1=field2 or field1>field2 - private boolean explanSpecialCondWithBothSidesAreProperty(SQLBinaryOpExpr bExpr, Where where) + private boolean explainSpecialCondWithBothSidesAreProperty(SQLBinaryOpExpr bExpr, Where where) throws SqlParseException { //join is not support if ((bExpr.getLeft() instanceof SQLPropertyExpr || bExpr.getLeft() instanceof SQLIdentifierExpr) @@ -185,7 +185,7 @@ private boolean explanSpecialCondWithBothSidesAreProperty(SQLBinaryOpExpr bExpr, sqlMethodInvokeExpr.addParameter(new SQLCharExpr( "doc['" + leftProperty + "'].value " + operator + " doc['" + rightProperty + "'].value")); - explanCond("AND", sqlMethodInvokeExpr, where); + explainCond("AND", sqlMethodInvokeExpr, where); return true; } return false; @@ -226,11 +226,11 @@ private void routeCond(SQLBinaryOpExpr bExpr, SQLExpr sub, Where where) throws S parseWhere(((SQLNotExpr) sub).getExpr(), subWhere); negateWhere(subWhere); } else { - explanCond(bExpr.getOperator().name, sub, where); + explainCond(bExpr.getOperator().name, sub, where); } } - private void explanCond(String opear, SQLExpr expr, Where where) throws SqlParseException { + private void explainCond(String opear, SQLExpr expr, Where where) throws SqlParseException { if (expr instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr soExpr = (SQLBinaryOpExpr) expr; boolean methodAsOpear = false; diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryIT.java index 259a453bdc..a04331cd23 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryIT.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/esintgtest/QueryIT.java @@ -1740,6 +1740,32 @@ public void backticksQuotedAliasInJDBCResponseTest() { assertTrue(response.contains("\"alias\": \"name\"")); } + @Test + public void caseWhenSwitchTest() throws IOException { + JSONObject response = executeQuery("SELECT CASE age " + + "WHEN '30' THEN '1' " + + "WHEN '40' THEN '2' " + + "ELSE '0' END AS cases FROM " + TEST_INDEX_ACCOUNT + " WHERE age IS NOT NULL"); + JSONObject hit = getHits(response).getJSONObject(0); + String age = hit.query("/_source/age").toString(); + String cases = age.equals("30") ? "1" : age.equals("40") ? "2" : "0"; + + assertThat(cases, equalTo(hit.query("/fields/cases/0"))); + } + + @Test + public void caseWhenJdbcResponseTest() { + String response = executeQuery("SELECT CASE age " + + "WHEN '30' THEN 'age is 30' " + + "WHEN '40' THEN 'age is 40' " + + "ELSE 'NA' END AS cases FROM " + TEST_INDEX_ACCOUNT + " WHERE age is not null", "jdbc"); + assertTrue( + response.contains("age is 30") || + response.contains("age is 40") || + response.contains("NA") + ); + } + private String getScrollId(JSONObject response) { return response.getString("_scroll_id"); } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/unittest/parser/SqlParserTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/unittest/parser/SqlParserTest.java index 26e7be4a97..2edb7d7bbc 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/unittest/parser/SqlParserTest.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/unittest/parser/SqlParserTest.java @@ -36,7 +36,9 @@ import com.amazon.opendistroforelasticsearch.sql.parser.SqlParser; import com.amazon.opendistroforelasticsearch.sql.query.maker.QueryMaker; import com.amazon.opendistroforelasticsearch.sql.query.multi.MultiQuerySelect; +import com.amazon.opendistroforelasticsearch.sql.util.CheckScriptContents; import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder.ScriptField; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -1116,6 +1118,22 @@ public void caseWhenTestWithouhtElseExpr() throws SqlParseException { } + @Test + public void caseWhenSwitchTest() { + String query = "SELECT CASE weather " + + "WHEN 'Sunny' THEN '0' " + + "WHEN 'Rainy' THEN '1' " + + "ELSE 'NA' END AS case " + + "FROM t"; + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + Assert.assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, + "doc['weather'].value=='Sunny'" + ) + ); + } + @Test public void castToIntTest() throws Exception { String query = "select cast(age as int) from "+ TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10";