From 79ff0ad2a4ddd5ea99431bbddc93325dbf13f67c Mon Sep 17 00:00:00 2001 From: xy720 <22125576+xy720@users.noreply.github.com> Date: Fri, 22 Nov 2019 15:01:53 +0800 Subject: [PATCH] Add pipes_as_concat_mode (#2252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit will add a new sql mode named MODE_PIPES_AS_CONCAT: Description: 1、If this mode is active, '||' will be handled different from the original way ('||' and 'or' are seen as the same symbols in Doris) that it can be used to concat two exps and returns a new string. For example, 'a' || 'b' = 'ab' and 1 || 0 = '10'. 2. User can active this mode by "SET sql_mode = PIPES_AS_CONCAT", and deactive it by "SET sql_mode = '' ". --- .../cn/administrator-guide/sql-mode.md | 4 +- .../en/administrator-guide/sql-mode_EN.md | 4 +- fe/src/main/cup/sql_parser.cup | 24 +++++- fe/src/main/jflex/sql_scanner.flex | 8 +- .../apache/doris/analysis/SqlModeTest.java | 79 +++++++++++++++++++ 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 fe/src/test/java/org/apache/doris/analysis/SqlModeTest.java diff --git a/docs/documentation/cn/administrator-guide/sql-mode.md b/docs/documentation/cn/administrator-guide/sql-mode.md index 948ce96687..43220bbd94 100644 --- a/docs/documentation/cn/administrator-guide/sql-mode.md +++ b/docs/documentation/cn/administrator-guide/sql-mode.md @@ -60,7 +60,9 @@ show session variables ## 已支持mode -(后续补充) +1. `PIPES_AS_CONCAT` + + 在此模式下,'||'符号是一种字符串连接符号(同CONCAT()函数),而不是'OR'符号的同义词。(e.g., `'a'||'b' = 'ab'`, `1||0 = '10'`) ## 复合mode diff --git a/docs/documentation/en/administrator-guide/sql-mode_EN.md b/docs/documentation/en/administrator-guide/sql-mode_EN.md index 0c1ea0296a..58db62c4c4 100644 --- a/docs/documentation/en/administrator-guide/sql-mode_EN.md +++ b/docs/documentation/en/administrator-guide/sql-mode_EN.md @@ -60,7 +60,9 @@ show session variables ## supported mode -(Work in progress) +1. `PIPES_AS_CONCAT` + + Treat '||' as a string concatenation operator (same as CONCAT()) rather than as a synonym for OR. (e.g., `'a'||'b' = 'ab'`, `1||0 = '10'`) ## combine mode diff --git a/fe/src/main/cup/sql_parser.cup b/fe/src/main/cup/sql_parser.cup index d47e86edb7..f1f8a415dc 100644 --- a/fe/src/main/cup/sql_parser.cup +++ b/fe/src/main/cup/sql_parser.cup @@ -210,7 +210,7 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_A KW_LOCAL, KW_LOCATION, KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_NAME, KW_NAMES, KW_NEGATIVE, KW_NO, KW_NOT, KW_NULL, KW_NULLS, - KW_OBSERVER, KW_OFFSET, KW_ON, KW_ONLY, KW_OPEN, KW_OR, KW_ORDER, KW_OUTER, KW_OVER, + KW_OBSERVER, KW_OFFSET, KW_ON, KW_ONLY, KW_OPEN, KW_OR, KW_PIPE, KW_ORDER, KW_OUTER, KW_OVER, KW_PARTITION, KW_PARTITIONS, KW_PATH, KW_PRECEDING, KW_PASSWORD, KW_PLUGIN, KW_PLUGINS, KW_PRIMARY, @@ -294,6 +294,7 @@ nonterminal Expr expr, non_pred_expr, arithmetic_expr, timestamp_arithmetic_expr nonterminal Expr set_expr_or_default; nonterminal ArrayList expr_list, values, row_value, opt_values; nonterminal ArrayList func_arg_list; +nonterminal ArrayList expr_pipe_list; nonterminal String select_alias, opt_table_alias; nonterminal ArrayList ident_list, opt_using_partition; nonterminal ClusterName cluster_name; @@ -428,6 +429,7 @@ precedence left KW_NOT, NOT; precedence left KW_BETWEEN, KW_IN, KW_IS, KW_EXISTS; precedence left KW_LIKE, KW_REGEXP; precedence left EQUAL, LESSTHAN, GREATERTHAN; +precedence left KW_PIPE; precedence left ADD, SUBTRACT; precedence left AT, STAR, DIVIDE, MOD, KW_DIV; precedence left BITAND, BITOR, BITXOR, BITNOT; @@ -3500,6 +3502,11 @@ non_pred_expr ::= /* Since "IF" is a keyword, need to special case this function */ | KW_IF LPAREN expr_list:exprs RPAREN {: RESULT = new FunctionCallExpr("if", exprs); :} + /* For the case like e1 || e2 || e3 ... */ + | expr_pipe_list:exprs + {: + RESULT = new FunctionCallExpr("concat", exprs); + :} | cast_expr:c {: RESULT = c; :} | case_expr:c @@ -3538,6 +3545,21 @@ non_pred_expr ::= {: RESULT = new BoolLiteral(false); :} ; +expr_pipe_list ::= + expr:e1 KW_PIPE expr:e2 + {: + ArrayList list = new ArrayList(); + list.add(e1); + list.add(e2); + RESULT = list; + :} + | expr_pipe_list:list KW_PIPE expr:e + {: + list.add(e); + RESULT = list; + :} + ; + func_arg_list ::= expr:item {: diff --git a/fe/src/main/jflex/sql_scanner.flex b/fe/src/main/jflex/sql_scanner.flex index 1885d65167..a9668c2d6d 100644 --- a/fe/src/main/jflex/sql_scanner.flex +++ b/fe/src/main/jflex/sql_scanner.flex @@ -31,6 +31,7 @@ import java.util.ArrayList; import org.apache.doris.analysis.SqlParserSymbols; import org.apache.doris.common.util.SqlUtils; +import org.apache.doris.qe.SqlModeHelper; %% @@ -352,7 +353,7 @@ import org.apache.doris.common.util.SqlUtils; keywordMap.put("with", new Integer(SqlParserSymbols.KW_WITH)); keywordMap.put("work", new Integer(SqlParserSymbols.KW_WORK)); keywordMap.put("write", new Integer(SqlParserSymbols.KW_WRITE)); - keywordMap.put("||", new Integer(SqlParserSymbols.KW_OR)); + keywordMap.put("||", new Integer(SqlParserSymbols.KW_PIPE)); } // map from token id to token description @@ -547,6 +548,11 @@ EndOfLineComment = "--" !({HintContent}|{ContainsLineTerminator}) {LineTerminato Integer kw_id = keywordMap.get(text.toLowerCase()); /* Integer kw_id = keywordMap.get(text); */ if (kw_id != null) { + // if MODE_PIPES_AS_CONCAT is not active, treat '||' symbol as same as 'or' symbol + if ((kw_id == SqlParserSymbols.KW_PIPE) && + ((this.sql_mode & SqlModeHelper.MODE_PIPES_AS_CONCAT) == 0)) { + return newToken(SqlParserSymbols.KW_OR, text); + } return newToken(kw_id.intValue(), text); } else { return newToken(SqlParserSymbols.IDENT, text); diff --git a/fe/src/test/java/org/apache/doris/analysis/SqlModeTest.java b/fe/src/test/java/org/apache/doris/analysis/SqlModeTest.java new file mode 100644 index 0000000000..1042fba7ea --- /dev/null +++ b/fe/src/test/java/org/apache/doris/analysis/SqlModeTest.java @@ -0,0 +1,79 @@ +// 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.doris.analysis; + +import org.apache.doris.qe.SqlModeHelper; +import org.junit.Assert; +import org.junit.Test; + +import java.io.StringReader; + +public class SqlModeTest { + + @Test + public void testScannerConstructor() { + String stmt = new String("SELECT * FROM db1.tbl1 WHERE name = 'BILL GATES'"); + SqlParser parser = new SqlParser(new SqlScanner(new StringReader(stmt))); + SelectStmt selectStmt = null; + try { + selectStmt = (SelectStmt) parser.parse().value; + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Assert.assertEquals("SELECT FROM `db1`.`tbl1` WHERE `name` = 'BILL GATES'", selectStmt.toSql()); + + parser = new SqlParser(new SqlScanner(new StringReader(stmt), SqlModeHelper.MODE_DEFAULT)); + try { + selectStmt = (SelectStmt) parser.parse().value; + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Assert.assertEquals("SELECT FROM `db1`.`tbl1` WHERE `name` = 'BILL GATES'", selectStmt.toSql()); + } + + @Test + public void testPipesAsConcatMode() { + // Mode Active + String stmt = new String("SELECT 'a' || 'b' || 'c'"); + SqlParser parser = new SqlParser(new SqlScanner(new StringReader(stmt), SqlModeHelper.MODE_PIPES_AS_CONCAT)); + SelectStmt selectStmt = null; + try { + selectStmt = (SelectStmt) parser.parse().value; + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Expr expr = selectStmt.getSelectList().getItems().get(0).getExpr(); + if (!(expr instanceof FunctionCallExpr)) { + Assert.fail("Mode not working"); + } + Assert.assertEquals("concat('a', 'b', 'c')", expr.toSql()); + + // Mode DeActive + parser = new SqlParser(new SqlScanner(new StringReader(stmt), SqlModeHelper.MODE_DEFAULT)); + try { + selectStmt = (SelectStmt) parser.parse().value; + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + expr = selectStmt.getSelectList().getItems().get(0).getExpr(); + if (!(expr instanceof CompoundPredicate)) { + Assert.fail(); + } + Assert.assertEquals("(('a') OR ('b')) OR ('c')", expr.toSql()); + } +}