This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-doris.git
The following commit(s) were added to refs/heads/master by this push: new 70825ce [Feature] Support alias function (#6261) 70825ce is described below commit 70825ce846be69560630ca80892f8973e9bd0eb2 Author: qiye <jianliang5...@gmail.com> AuthorDate: Sat Aug 7 21:29:13 2021 +0800 [Feature] Support alias function (#6261) Implement #6260. Add alias function type. --- docs/.vuepress/sidebar/en.js | 1 + docs/.vuepress/sidebar/zh-CN.js | 1 + .../sql-reference/sql-functions/digital-masking.md | 56 +++++ .../Data Definition/create-function.md | 83 ++++--- .../Data Definition/show-functions.md | 8 +- .../sql-reference/sql-functions/digital-masking.md | 56 +++++ .../Data Definition/create-function.md | 47 ++-- .../Data Definition/show-functions.md | 8 +- fe/fe-core/src/main/cup/sql_parser.cup | 13 +- .../java/org/apache/doris/analysis/Analyzer.java | 2 + .../apache/doris/analysis/CreateFunctionStmt.java | 57 ++++- .../apache/doris/analysis/FunctionCallExpr.java | 93 +++++++- .../org/apache/doris/analysis/LiteralExpr.java | 35 +++ .../org/apache/doris/catalog/AliasFunction.java | 258 +++++++++++++++++++++ .../java/org/apache/doris/catalog/Function.java | 11 +- .../java/org/apache/doris/catalog/FunctionSet.java | 1 + .../doris/rewrite/RewriteAliasFunctionRule.java | 48 ++++ fe/fe-core/src/main/jflex/sql_scanner.flex | 2 + .../apache/doris/catalog/CreateFunctionTest.java | 23 ++ 19 files changed, 749 insertions(+), 54 deletions(-) diff --git a/docs/.vuepress/sidebar/en.js b/docs/.vuepress/sidebar/en.js index 682fd9f..34bca23 100644 --- a/docs/.vuepress/sidebar/en.js +++ b/docs/.vuepress/sidebar/en.js @@ -399,6 +399,7 @@ module.exports = [ }, "window-function", "cast", + "digital-masking", ], }, { diff --git a/docs/.vuepress/sidebar/zh-CN.js b/docs/.vuepress/sidebar/zh-CN.js index 0ada9ad..86ac1a1 100644 --- a/docs/.vuepress/sidebar/zh-CN.js +++ b/docs/.vuepress/sidebar/zh-CN.js @@ -404,6 +404,7 @@ module.exports = [ }, "window-function", "cast", + "digital-masking", ], }, { diff --git a/docs/en/sql-reference/sql-functions/digital-masking.md b/docs/en/sql-reference/sql-functions/digital-masking.md new file mode 100644 index 0000000..bd2b22b --- /dev/null +++ b/docs/en/sql-reference/sql-functions/digital-masking.md @@ -0,0 +1,56 @@ +--- +{ + "title": "DIGITAL-MASKING", + "language": "en" +} +--- + +<!-- +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. +--> + +# DIGITAL_MASKING + +## description + +### Syntax + +``` +digital_masking(digital_number) +``` + +Alias function, the original function is `concat(left(id,3),'****',right(id,4))`. + +Desensitizes the input `digital_number` and returns the result after masking desensitization. + +## example + +1. Desensitize the cell phone number + + ```sql + mysql> select digital_masking(13812345678); + +------------------------------+ + | digital_masking(13812345678) | + +------------------------------+ + | 138****5678 | + +------------------------------+ + ``` + +## keyword + +DIGITAL_MASKING diff --git a/docs/en/sql-reference/sql-statements/Data Definition/create-function.md b/docs/en/sql-reference/sql-statements/Data Definition/create-function.md index fc0a5f0..7eed4e4 100644 --- a/docs/en/sql-reference/sql-statements/Data Definition/create-function.md +++ b/docs/en/sql-reference/sql-statements/Data Definition/create-function.md @@ -25,50 +25,60 @@ under the License. --> # CREATE FUNCTION -##Description +## Description ### Syntax ``` -CREATE [AGGREGATE] FUNCTION function_name - (angry type [...]) - RETURNS ret_type - [INTERMEDIATE inter_type] - [PROPERTIES ("key" = "value" [, ...]) ] +CREATE [AGGREGATE] [ALIAS] FUNCTION function_name + (arg_type [, ...]) + [RETURNS ret_type] + [INTERMEDIATE inter_type] + [WITH PARAMETER(param [,...]) AS origin_function] + [PROPERTIES ("key" = "value" [, ...]) ] ``` ### Parameters ->`AGGREGATE`: If this is the case, it means that the created function is an aggregate function, otherwise it is a scalar function. +> `AGGREGATE`: If this is the case, it means that the created function is an aggregate function. > ->`Function_name`: To create the name of the function, you can include the name of the database. For example: `db1.my_func'. -> ->` arg_type': The parameter type of the function is the same as the type defined at the time of table building. Variable-length parameters can be represented by `,...`. If it is a variable-length type, the type of the variable-length part of the parameters is the same as the last non-variable-length parameter type. +> `ALIAS`: If this is the case, it means that the created function is an alias function. +> +> If the above two items are not present, it means that the created function is a scalar function. +> +> `Function_name`: To create the name of the function, you can include the name of the database. For example: `db1.my_func'. > ->`ret_type`: Function return type. +> `arg_type`: The parameter type of the function is the same as the type defined at the time of table building. Variable-length parameters can be represented by `,...`. If it is a variable-length type, the type of the variable-length part of the parameters is the same as the last non-variable-length parameter type. +> **NOTICE**: `ALIAS FUNCTION` variable-length parameters are not supported, and there is at least one parameter. +> +> `ret_type`: Required for creating a new function. This parameter is not required if you are aliasing an existing function. > ->`Inter_type`: A data type used to represent the intermediate stage of an aggregate function. +> `inter_type`: A data type used to represent the intermediate stage of an aggregate function. +> +> `param`: The parameter used to represent the alias function, containing at least one. +> +> `origin_function`: Used to represent the original function corresponding to the alias function. > ->`properties`: Used to set properties related to this function. Properties that can be set include +> `properties`: Used to set properties related to aggregate function and scalar function. Properties that can be set include > -> "Object_file": Custom function dynamic library URL path, currently only supports HTTP/HTTPS protocol, this path needs to remain valid throughout the life cycle of the function. This option is mandatory +> "Object_file": Custom function dynamic library URL path, currently only supports HTTP/HTTPS protocol, this path needs to remain valid throughout the life cycle of the function. This option is mandatory > -> "symbol": Function signature of scalar functions for finding function entries from dynamic libraries. This option is mandatory for scalar functions +> "symbol": Function signature of scalar functions for finding function entries from dynamic libraries. This option is mandatory for scalar functions > -> "init_fn": Initialization function signature of aggregate function. Necessary for aggregation functions +> "init_fn": Initialization function signature of aggregate function. Necessary for aggregation functions > -> "update_fn": Update function signature of aggregate function. Necessary for aggregation functions +> "update_fn": Update function signature of aggregate function. Necessary for aggregation functions > -> "merge_fn": Merge function signature of aggregate function. Necessary for aggregation functions +> "merge_fn": Merge function signature of aggregate function. Necessary for aggregation functions > -> "serialize_fn": Serialized function signature of aggregate function. For aggregation functions, it is optional, and if not specified, the default serialization function will be used +> "serialize_fn": Serialized function signature of aggregate function. For aggregation functions, it is optional, and if not specified, the default serialization function will be used > -> "finalize_fn": A function signature that aggregates functions to obtain the final result. For aggregation functions, it is optional. If not specified, the default fetch result function will be used. +> "finalize_fn": A function signature that aggregates functions to obtain the final result. For aggregation functions, it is optional. If not specified, the default fetch result function will be used. > -> "md5": The MD5 value of the function dynamic link library, which is used to verify that the downloaded content is correct. This option is optional +> "md5": The MD5 value of the function dynamic link library, which is used to verify that the downloaded content is correct. This option is optional > -> "prepare_fn": Function signature of the prepare function for finding the entry from the dynamic library. This option is optional for custom functions +> "prepare_fn": Function signature of the prepare function for finding the entry from the dynamic library. This option is optional for custom functions > -> "close_fn": Function signature of the close function for finding the entry from the dynamic library. This option is optional for custom functions +> "close_fn": Function signature of the close function for finding the entry from the dynamic library. This option is optional for custom functions This statement creates a custom function. Executing this command requires that the user have `ADMIN` privileges. @@ -100,12 +110,29 @@ If the `function_name` contains the database name, the custom function will be c ``` CREATE AGGREGATE FUNCTION my_count (BIGINT) RETURNS BIGINT PROPERTIES ( - "init_fn"= "_ZN9doris_udf6AddUdfEPNS_15FunctionContextERKNS_6IntValES4_", - "update_fn" = "zn9dorisudf11CountupdateepnsFunctionContexterknsIntvalepnsbigintvale", - "merge_fn" = "zn9dorisudf10CountMergeepnsFunctionContexterknsBigintvaleps2 - "finalize_fn" = "zn9dorisudf13CountFinalizepnsFunctionContexterknsBigintvale", - "object_file" = "http://host:port/libudasample.so" + "init_fn"="_ZN9doris_udf9CountInitEPNS_15FunctionContextEPNS_9BigIntValE", + "update_fn"="_ZN9doris_udf11CountUpdateEPNS_15FunctionContextERKNS_6IntValEPNS_9BigIntValE", + "merge_fn"="_ZN9doris_udf10CountMergeEPNS_15FunctionContextERKNS_9BigIntValEPS2_", + "finalize_fn"="_ZN9doris_udf13CountFinalizeEPNS_15FunctionContextERKNS_9BigIntValE", + "object_file"="http://host:port/libudasample.so" ); ``` + +4. Create a scalar function with variable length parameters + + ``` + CREATE FUNCTION strconcat(varchar, ...) RETURNS varchar properties ( + "symbol" = "_ZN9doris_udf6StrConcatUdfEPNS_15FunctionContextERKNS_6IntValES4_", + "object_file" = "http://host:port/libmyStrConcat.so" + ); + ``` + +5. Create a custom alias function + + ``` + CREATE ALIAS FUNCTION id_masking(INT) WITH PARAMETER(id) + AS CONCAT(LEFT(id, 3), '****', RIGHT(id, 4)); + ``` + ## keyword CREATE,FUNCTION diff --git a/docs/en/sql-reference/sql-statements/Data Definition/show-functions.md b/docs/en/sql-reference/sql-statements/Data Definition/show-functions.md index c53878c..b942046 100644 --- a/docs/en/sql-reference/sql-statements/Data Definition/show-functions.md +++ b/docs/en/sql-reference/sql-statements/Data Definition/show-functions.md @@ -59,8 +59,14 @@ Intermediate Type: NULL Function Type: Aggregate Intermediate Type: NULL Properties: {"object_file":"http://host:port/libudasample.so","finalize_fn":"_ZN9doris_udf13CountFinalizeEPNS_15FunctionContextERKNS_9BigIntValE","init_fn":"_ZN9doris_udf9CountInitEPNS_15FunctionContextEPNS_9BigIntValE","merge_fn":"_ZN9doris_udf10CountMergeEPNS_15FunctionContextERKNS_9BigIntValEPS2_","md5":"37d185f80f95569e2676da3d5b5b9d2f","update_fn":"_ZN9doris_udf11CountUpdateEPNS_15FunctionContextERKNS_6IntValEPNS_9BigIntValE"} +*************************** 3. row *************************** + Signature: id_masking(INT) + Return Type: VARCHAR + Function Type: Alias +Intermediate Type: NULL + Properties: {"parameter":"id","origin_function":"concat(left(`id`, 3), `****`, right(`id`, 4))"} -2 rows in set (0.00 sec) +3 rows in set (0.00 sec) mysql> show builtin functions in testDb like 'year%'; +---------------+ | Function Name | diff --git a/docs/zh-CN/sql-reference/sql-functions/digital-masking.md b/docs/zh-CN/sql-reference/sql-functions/digital-masking.md new file mode 100644 index 0000000..32ea5a3 --- /dev/null +++ b/docs/zh-CN/sql-reference/sql-functions/digital-masking.md @@ -0,0 +1,56 @@ +--- +{ + "title": "DIGITAL-MASKING", + "language": "zh-CN" +} +--- + +<!-- +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. +--> + +# DIGITAL_MASKING + +## description + +### Syntax + +``` +digital_masking(digital_number) +``` + +别名函数,原始函数为 `concat(left(id,3),'****',right(id,4))`。 + +将输入的 `digital_number` 进行脱敏处理,返回遮盖脱敏后的结果。 + +## example + +1. 将手机号码进行脱敏处理 + + ```sql + mysql> select digital_masking(13812345678); + +------------------------------+ + | digital_masking(13812345678) | + +------------------------------+ + | 138****5678 | + +------------------------------+ + ``` + +## keyword + +DIGITAL_MASKING diff --git a/docs/zh-CN/sql-reference/sql-statements/Data Definition/create-function.md b/docs/zh-CN/sql-reference/sql-statements/Data Definition/create-function.md index 0c3f3b7..8582626 100644 --- a/docs/zh-CN/sql-reference/sql-statements/Data Definition/create-function.md +++ b/docs/zh-CN/sql-reference/sql-statements/Data Definition/create-function.md @@ -25,30 +25,40 @@ under the License. --> # CREATE FUNCTION -## description +## Description ### Syntax ``` -CREATE [AGGREGATE] FUNCTION function_name +CREATE [AGGREGATE] [ALIAS] FUNCTION function_name (arg_type [, ...]) - RETURNS ret_type + [RETURNS ret_type] [INTERMEDIATE inter_type] + [WITH PARAMETER(param [,...]) AS origin_function] [PROPERTIES ("key" = "value" [, ...]) ] ``` ### Parameters -> `AGGREGATE`: 如果有此项,表示的是创建的函数是一个聚合函数,否则创建的是一个标量函数。 -> +> `AGGREGATE`: 如果有此项,表示的是创建的函数是一个聚合函数。 +> +> `ALIAS`:如果有此项,表示的是创建的函数是一个别名函数。 +> +> 如果没有上述两项,表示创建的函数是一个标量函数 +> > `function_name`: 要创建函数的名字, 可以包含数据库的名字。比如:`db1.my_func`。 > > `arg_type`: 函数的参数类型,与建表时定义的类型一致。变长参数时可以使用`, > ...`来表示,如果是变长类型,那么变长部分参数的类型与最后一个非变长参数类型一致。 +> **注意**:`ALIAS FUNCTION` 不支持变长参数,且至少有一个参数。 > -> `ret_type`: 函数返回类型。 +> `ret_type`: 对创建新的函数来说,是必填项。如果是给已有函数取别名则可不用填写该参数。 > > `inter_type`: 用于表示聚合函数中间阶段的数据类型。 > -> `properties`: 用于设定此函数相关属性,能够设置的属性包括 +> `param`:用于表示别名函数的参数,至少包含一个。 +> +> `origin_function`:用于表示别名函数对应的原始函数。 +> +> `properties`: 用于设定聚合函数和标量函数相关属性,能够设置的属性包括 > > "object_file": 自定义函数动态库的URL路径,当前只支持 HTTP/HTTPS > 协议,此路径需要在函数整个生命周期内保持有效。此选项为必选项 > @@ -97,7 +107,7 @@ CREATE [AGGREGATE] FUNCTION function_name ); ``` -2. 创建一个自定义聚合函数 +3. 创建一个自定义聚合函数 ``` CREATE AGGREGATE FUNCTION my_count (BIGINT) RETURNS BIGINT PROPERTIES ( @@ -109,14 +119,21 @@ CREATE [AGGREGATE] FUNCTION function_name ); ``` -3. 创建一个变长参数的标量函数 +4. 创建一个变长参数的标量函数 + + ``` + CREATE FUNCTION strconcat(varchar, ...) RETURNS varchar properties ( + "symbol" = "_ZN9doris_udf6StrConcatUdfEPNS_15FunctionContextERKNS_6IntValES4_", + "object_file" = "http://host:port/libmyStrConcat.so" + ); + ``` + +5. 创建一个自定义别名函数 - ``` - CREATE FUNCTION strconcat(varchar, ...) RETURNS varchar properties ( - "symbol" = "_ZN9doris_udf6StrConcatUdfEPNS_15FunctionContextERKNS_6IntValES4_", - "object_file" = "http://host:port/libmyStrConcat.so" - ); - ``` + ``` + CREATE ALIAS FUNCTION id_masking(INT) WITH PARAMETER(id) + AS CONCAT(LEFT(id, 3), '****', RIGHT(id, 4)); + ``` ## keyword diff --git a/docs/zh-CN/sql-reference/sql-statements/Data Definition/show-functions.md b/docs/zh-CN/sql-reference/sql-statements/Data Definition/show-functions.md index b8808ba..1220de2 100644 --- a/docs/zh-CN/sql-reference/sql-statements/Data Definition/show-functions.md +++ b/docs/zh-CN/sql-reference/sql-statements/Data Definition/show-functions.md @@ -60,8 +60,14 @@ Intermediate Type: NULL Function Type: Aggregate Intermediate Type: NULL Properties: {"object_file":"http://host:port/libudasample.so","finalize_fn":"_ZN9doris_udf13CountFinalizeEPNS_15FunctionContextERKNS_9BigIntValE","init_fn":"_ZN9doris_udf9CountInitEPNS_15FunctionContextEPNS_9BigIntValE","merge_fn":"_ZN9doris_udf10CountMergeEPNS_15FunctionContextERKNS_9BigIntValEPS2_","md5":"37d185f80f95569e2676da3d5b5b9d2f","update_fn":"_ZN9doris_udf11CountUpdateEPNS_15FunctionContextERKNS_6IntValEPNS_9BigIntValE"} +*************************** 3. row *************************** + Signature: id_masking(INT) + Return Type: VARCHAR + Function Type: Alias +Intermediate Type: NULL + Properties: {"parameter":"id","origin_function":"concat(left(`id`, 3), `****`, right(`id`, 4))"} -2 rows in set (0.00 sec) +3 rows in set (0.00 sec) mysql> show builtin functions in testDb like 'year%'; +---------------+ | Function Name | diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup index f71647a..0b21b71 100644 --- a/fe/fe-core/src/main/cup/sql_parser.cup +++ b/fe/fe-core/src/main/cup/sql_parser.cup @@ -233,7 +233,7 @@ parser code {: :}; // Total keywords of doris -terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_AND, KW_ANTI, KW_APPEND, KW_AS, KW_ASC, KW_AUTHORS, KW_ARRAY, +terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALIAS, KW_ALL, KW_ALTER, KW_AND, KW_ANTI, KW_APPEND, KW_AS, KW_ASC, KW_AUTHORS, KW_ARRAY, KW_BACKEND, KW_BACKUP, KW_BETWEEN, KW_BEGIN, KW_BIGINT, KW_BITMAP, KW_BITMAP_UNION, KW_BOOLEAN, KW_BROKER, KW_BACKENDS, KW_BY, KW_BUILTIN, KW_CANCEL, KW_CASE, KW_CAST, KW_CHAIN, KW_CHAR, KW_CHARSET, KW_CHECK, KW_CLUSTER, KW_CLUSTERS, KW_COLLATE, KW_COLLATION, KW_COLUMN, KW_COLON, KW_COLUMNS, KW_COMMENT, KW_COMMIT, KW_COMMITTED, @@ -254,7 +254,7 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_A KW_MAP, KW_MATERIALIZED, KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, KW_MINUTE, KW_MINUS, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_MONTH, KW_NAME, KW_NAMED_STRUCT, 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_OUTFILE, KW_OVER, - KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, KW_LDAP_ADMIN_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING, + KW_PARAMETER, KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, KW_LDAP_ADMIN_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING, KW_PLUGIN, KW_PLUGINS, KW_PROC, KW_PROCEDURE, KW_PROCESSLIST, KW_PROFILE, KW_PROPERTIES, KW_PROPERTY, KW_QUERY, KW_QUOTA, @@ -1145,6 +1145,11 @@ create_stmt ::= {: RESULT = new CreateFunctionStmt(isAggregate, functionName, args, returnType, intermediateType, properties); :} + | KW_CREATE KW_ALIAS KW_FUNCTION function_name:functionName LPAREN func_args_def:args RPAREN + KW_WITH KW_PARAMETER LPAREN ident_list:parameters RPAREN KW_AS expr:func + {: + RESULT = new CreateFunctionStmt(functionName, args, parameters, func); + :} /* Table */ | KW_CREATE opt_external:isExternal KW_TABLE opt_if_not_exists:ifNotExists table_name:name KW_LIKE table_name:existed_name {: @@ -4935,6 +4940,8 @@ keyword ::= {: RESULT = id; :} | KW_AGGREGATE:id {: RESULT = id; :} + | KW_ALIAS:id + {: RESULT = id; :} | KW_AUTHORS:id {: RESULT = id; :} | KW_ARRAY:id @@ -5083,6 +5090,8 @@ keyword ::= {: RESULT = id; :} | KW_OPEN:id {: RESULT = id; :} + | KW_PARAMETER:id + {: RESULT = id; :} | KW_PARTITIONS:id {: RESULT = id; :} | KW_PASSWORD:id diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java index 7c7888c..48b147a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java @@ -41,6 +41,7 @@ import org.apache.doris.rewrite.ExprRewriteRule; import org.apache.doris.rewrite.ExprRewriter; import org.apache.doris.rewrite.ExtractCommonFactorsRule; import org.apache.doris.rewrite.FoldConstantsRule; +import org.apache.doris.rewrite.RewriteAliasFunctionRule; import org.apache.doris.rewrite.RewriteEncryptKeyRule; import org.apache.doris.rewrite.RewriteFromUnixTimeRule; import org.apache.doris.rewrite.NormalizeBinaryPredicatesRule; @@ -270,6 +271,7 @@ public class Analyzer { rules.add(RewriteFromUnixTimeRule.INSTANCE); rules.add(SimplifyInvalidDateBinaryPredicatesDateRule.INSTANCE); rules.add(RewriteEncryptKeyRule.INSTANCE); + rules.add(RewriteAliasFunctionRule.INSTANCE); List<ExprRewriteRule> onceRules = Lists.newArrayList(); onceRules.add(ExtractCommonFactorsRule.INSTANCE); exprRewriter_ = new ExprRewriter(rules, onceRules); diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java index 2a4f7ab..6e376ad 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java @@ -18,9 +18,11 @@ package org.apache.doris.analysis; import org.apache.doris.catalog.AggregateFunction; +import org.apache.doris.catalog.AliasFunction; import org.apache.doris.catalog.Catalog; import org.apache.doris.catalog.Function; import org.apache.doris.catalog.ScalarFunction; +import org.apache.doris.catalog.Type; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; @@ -31,6 +33,7 @@ import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import org.apache.commons.codec.binary.Hex; @@ -39,6 +42,7 @@ import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.List; import java.util.Map; // create a user define function @@ -58,10 +62,13 @@ public class CreateFunctionStmt extends DdlStmt { private final FunctionName functionName; private final boolean isAggregate; + private final boolean isAlias; private final FunctionArgsDef argsDef; private final TypeDef returnType; private TypeDef intermediateType; private final Map<String, String> properties; + private final List<String> parameters; + private final Expr originFunction; // needed item set after analyzed private String objectFile; @@ -83,11 +90,34 @@ public class CreateFunctionStmt extends DdlStmt { } else { this.properties = ImmutableSortedMap.copyOf(properties, String.CASE_INSENSITIVE_ORDER); } + this.isAlias = false; + this.parameters = ImmutableList.of(); + this.originFunction = null; + } + + public CreateFunctionStmt(FunctionName functionName, FunctionArgsDef argsDef, + List<String> parameters, Expr originFunction) { + this.functionName = functionName; + this.isAlias = true; + this.argsDef = argsDef; + if (parameters == null) { + this.parameters = ImmutableList.of(); + } else { + this.parameters = ImmutableList.copyOf(parameters); + } + this.originFunction = originFunction; + this.isAggregate = false; + this.returnType = new TypeDef(Type.VARCHAR); + this.properties = ImmutableSortedMap.of(); } public FunctionName getFunctionName() { return functionName; } public Function getFunction() { return function; } + public Expr getOriginFunction() { + return originFunction; + } + @Override public void analyze(Analyzer analyzer) throws UserException { super.analyze(analyzer); @@ -96,6 +126,8 @@ public class CreateFunctionStmt extends DdlStmt { // check if (isAggregate) { analyzeUda(); + } else if (isAlias) { + analyzeAliasFunction(); } else { analyzeUdf(); } @@ -112,6 +144,11 @@ public class CreateFunctionStmt extends DdlStmt { // check argument argsDef.analyze(analyzer); + // alias function does not need analyze following params + if (isAlias) { + return; + } + returnType.analyze(analyzer); if (intermediateType != null) { intermediateType.analyze(analyzer); @@ -197,18 +234,34 @@ public class CreateFunctionStmt extends DdlStmt { function.setChecksum(checksum); } + private void analyzeAliasFunction() throws AnalysisException { + function = AliasFunction.createFunction(functionName, argsDef.getArgTypes(), + Type.VARCHAR, argsDef.isVariadic(), parameters, originFunction); + ((AliasFunction) function).analyze(); + } + @Override public String toSql() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("CREATE "); if (isAggregate) { stringBuilder.append("AGGREGATE "); + } else if (isAlias) { + stringBuilder.append("ALIAS "); } + stringBuilder.append("FUNCTION "); stringBuilder.append(functionName.toString()); stringBuilder.append(argsDef.toSql()); - stringBuilder.append(" RETURNS "); - stringBuilder.append(returnType.toString()); + if (isAlias) { + stringBuilder.append(" WITH PARAMETER (") + .append(parameters.toString()) + .append(") AS ") + .append(originFunction.toSql()); + } else { + stringBuilder.append(" RETURNS "); + stringBuilder.append(returnType.toString()); + } if (properties.size() > 0) { stringBuilder.append(" PROPERTIES ("); int i = 0; diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java index a04cdb1..bda3af9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java @@ -19,6 +19,7 @@ package org.apache.doris.analysis; import org.apache.doris.catalog.AggregateFunction; import org.apache.doris.catalog.ArrayType; +import org.apache.doris.catalog.AliasFunction; import org.apache.doris.catalog.Catalog; import org.apache.doris.catalog.Database; import org.apache.doris.catalog.Function; @@ -71,6 +72,11 @@ public class FunctionCallExpr extends Expr { .add("stddev").add("stddev_val").add("stddev_samp") .add("variance").add("variance_pop").add("variance_pop").add("var_samp").add("var_pop").build(); private static final String ELEMENT_EXTRACT_FN_NAME = "%element_extract%"; + + // Save the functionCallExpr in the original statement + private Expr originStmtFnExpr; + + private boolean isRewrote = false; public void setIsAnalyticFnCall(boolean v) { isAnalyticFnCall = v; @@ -84,6 +90,10 @@ public class FunctionCallExpr extends Expr { return fnName; } + public FunctionParams getFnParams() { + return fnParams; + } + // only used restore from readFields. private FunctionCallExpr() { super(); @@ -184,15 +194,21 @@ public class FunctionCallExpr extends Expr { @Override public String toSqlImpl() { + Expr expr; + if (originStmtFnExpr != null) { + expr = originStmtFnExpr; + } else { + expr = this; + } StringBuilder sb = new StringBuilder(); - sb.append(fnName).append("("); - if (fnParams.isStar()) { + sb.append(((FunctionCallExpr) expr).fnName).append("("); + if (((FunctionCallExpr) expr).fnParams.isStar()) { sb.append("*"); } - if (fnParams.isDistinct()) { + if (((FunctionCallExpr) expr).fnParams.isDistinct()) { sb.append("DISTINCT "); } - sb.append(Joiner.on(", ").join(childrenToSql())).append(")"); + sb.append(Joiner.on(", ").join(expr.childrenToSql())).append(")"); return sb.toString(); } @@ -729,6 +745,75 @@ public class FunctionCallExpr extends Expr { } } + /** + * rewrite alias function to real function + * reset function name, function params and it's children to real function's + * @return + * @throws AnalysisException + */ + public Expr rewriteExpr() throws AnalysisException { + if (isRewrote) { + return this; + } + // clone a new functionCallExpr to rewrite + FunctionCallExpr retExpr = (FunctionCallExpr) clone(); + // clone origin function call expr in origin stmt + retExpr.originStmtFnExpr = clone(); + // clone alias function origin expr for alias + FunctionCallExpr oriExpr = (FunctionCallExpr) ((AliasFunction) retExpr.fn).getOriginFunction().clone(); + // reset fn name + retExpr.fnName = oriExpr.getFnName(); + // reset fn params + List<Expr> inputParamsExprs = retExpr.fnParams.exprs(); + List<String> parameters = ((AliasFunction) retExpr.fn).getParameters(); + Preconditions.checkArgument(inputParamsExprs.size() == parameters.size(), + "Alias function [" + retExpr.fn.getFunctionName().getFunction() + "] args number is not equal to it's definition"); + List<Expr> oriParamsExprs = oriExpr.fnParams.exprs(); + + // replace origin function params exprs' with input params expr depending on parameter name + for (int i = 0; i < oriParamsExprs.size(); i++) { + Expr expr = replaceParams(parameters, inputParamsExprs, oriParamsExprs.get(i)); + oriParamsExprs.set(i, expr); + } + + retExpr.fnParams = new FunctionParams(oriExpr.fnParams.isDistinct(), oriParamsExprs); + + // reset children + retExpr.children.clear(); + retExpr.children.addAll(oriExpr.getChildren()); + retExpr.isRewrote = true; + return retExpr; + } + + /** + * replace origin function expr and it's children with input params exprs depending on parameter name + * @param parameters + * @param inputParamsExprs + * @param oriExpr + * @return + * @throws AnalysisException + */ + private Expr replaceParams(List<String> parameters, List<Expr> inputParamsExprs, Expr oriExpr) throws AnalysisException { + for (int i = 0; i < oriExpr.getChildren().size(); i++) { + Expr retExpr = replaceParams(parameters, inputParamsExprs, oriExpr.getChild(i)); + oriExpr.setChild(i, retExpr); + } + if (oriExpr instanceof SlotRef) { + String columnName = ((SlotRef) oriExpr).getColumnName(); + int index = parameters.indexOf(columnName); + if (index != -1) { + return inputParamsExprs.get(index); + } + } + // Initialize literalExpr without type information, because literalExpr does not save type information + // when it is persisted, so after fe restart, read the image, + // it will be missing type and report an error during analyze. + if (oriExpr instanceof LiteralExpr && oriExpr.getType().equals(Type.INVALID)) { + oriExpr = LiteralExpr.init((LiteralExpr) oriExpr); + } + return oriExpr; + } + @Override public boolean isVectorized() { return false; diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java index f6ab8f6..6a8622d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java @@ -86,6 +86,41 @@ public abstract class LiteralExpr extends Expr implements Comparable<LiteralExpr return literalExpr; } + /** + * Init LiteralExpr's Type information + * only use in rewrite alias function + * @param expr + * @return + * @throws AnalysisException + */ + public static LiteralExpr init(LiteralExpr expr) throws AnalysisException { + Preconditions.checkArgument(expr.getType().equals(Type.INVALID)); + String value = expr.getStringValue(); + LiteralExpr literalExpr = null; + if (expr instanceof NullLiteral) { + literalExpr = new NullLiteral(); + } else if (expr instanceof BoolLiteral) { + literalExpr = new BoolLiteral(value); + } else if (expr instanceof IntLiteral) { + literalExpr = new IntLiteral(Long.parseLong(value)); + } else if (expr instanceof LargeIntLiteral) { + literalExpr = new LargeIntLiteral(value); + } else if (expr instanceof FloatLiteral) { + literalExpr = new FloatLiteral(value); + } else if (expr instanceof DecimalLiteral) { + literalExpr = new DecimalLiteral(value); + } else if (expr instanceof StringLiteral) { + literalExpr = new StringLiteral(value); + } else if (expr instanceof DateLiteral) { + literalExpr = new DateLiteral(value, expr.getType()); + } else { + throw new AnalysisException("Type[" + expr.getType().toSql() + "] not supported."); + } + + Preconditions.checkNotNull(literalExpr); + return literalExpr; + } + public static LiteralExpr createInfinity(Type type, boolean isMax) throws AnalysisException { Preconditions.checkArgument(!type.equals(Type.INVALID)); if (isMax) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/AliasFunction.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/AliasFunction.java new file mode 100644 index 0000000..6646eaf --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/AliasFunction.java @@ -0,0 +1,258 @@ +// 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.catalog; + +import org.apache.doris.analysis.Expr; +import org.apache.doris.analysis.FunctionCallExpr; +import org.apache.doris.analysis.FunctionName; +import org.apache.doris.analysis.SelectStmt; +import org.apache.doris.analysis.SlotRef; +import org.apache.doris.analysis.SqlParser; +import org.apache.doris.analysis.SqlScanner; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.io.Text; +import org.apache.doris.common.util.SqlParserUtils; +import org.apache.doris.qe.SqlModeHelper; +import org.apache.doris.thrift.TFunctionBinaryType; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Internal representation of an alias function. + */ +public class AliasFunction extends Function { + private static final Logger LOG = LogManager.getLogger(AliasFunction.class); + + private static final String DIGITAL_MASKING = "digital_masking"; + + private Expr originFunction; + private List<String> parameters = new ArrayList<>(); + + // Only used for serialization + protected AliasFunction() { + } + + public AliasFunction(FunctionName fnName, Type[] argTypes, Type retType, boolean hasVarArgs) { + super(fnName, argTypes, retType, hasVarArgs); + } + + public AliasFunction(FunctionName fnName, ArrayList<Type> argTypes, Type retType, boolean hasVarArgs) { + super(fnName, argTypes, retType, hasVarArgs); + } + + public static AliasFunction createFunction(FunctionName functionName, Type[] argTypes, Type retType, + boolean hasVarArgs, List<String> parameters, Expr originFunction) { + AliasFunction aliasFunction = new AliasFunction(functionName, argTypes, retType, hasVarArgs); + aliasFunction.setBinaryType(TFunctionBinaryType.NATIVE); + aliasFunction.setUserVisible(true); + aliasFunction.originFunction = originFunction; + aliasFunction.parameters = parameters; + return aliasFunction; + } + + public static void initBuiltins(FunctionSet functionSet) { + String oriStmt = "select concat(left(id,3),'****',right(id,4));"; + try { + /** + * Please ensure that the condition checks in {@link #analyze} are satisfied + */ + functionSet.addBuiltin(createBuiltin(DIGITAL_MASKING, Lists.newArrayList(Type.INT), Type.VARCHAR, + false, Lists.newArrayList("id"), getExpr(oriStmt), true)); + } catch (AnalysisException e) { + LOG.error("Add builtin alias function error {}", e); + } + } + + public static Expr getExpr(String sql) throws AnalysisException { + SelectStmt parsedStmt; + // Parse statement with parser generated by CUP&FLEX + SqlScanner input = new SqlScanner(new StringReader(sql), SqlModeHelper.MODE_DEFAULT); + SqlParser parser = new SqlParser(input); + try { + parsedStmt = (SelectStmt) SqlParserUtils.getFirstStmt(parser); + } catch (Error e) { + LOG.info("error happened when parsing stmt {}", sql, e); + throw new AnalysisException("sql parsing error, please check your sql"); + } catch (AnalysisException e) { + String syntaxError = parser.getErrorMsg(sql); + LOG.info("analysis exception happened when parsing stmt {}, error: {}", + sql, syntaxError, e); + if (syntaxError == null) { + throw e; + } else { + throw new AnalysisException(syntaxError, e); + } + } catch (Exception e) { + // TODO(lingbin): we catch 'Exception' to prevent unexpected error, + // should be removed this try-catch clause future. + LOG.info("unexpected exception happened when parsing stmt {}, error: {}", + sql, parser.getErrorMsg(sql), e); + throw new AnalysisException("Unexpected exception: " + e.getMessage()); + } + + return parsedStmt.getSelectList().getItems().get(0).getExpr(); + } + + private static AliasFunction createBuiltin(String name, ArrayList<Type> argTypes, Type retType, + boolean hasVarArgs, List<String> parameters, Expr originFunction, + boolean userVisible) { + AliasFunction aliasFunction = new AliasFunction(new FunctionName(name), argTypes, retType, hasVarArgs); + aliasFunction.setBinaryType(TFunctionBinaryType.BUILTIN); + aliasFunction.setUserVisible(userVisible); + aliasFunction.originFunction = originFunction; + aliasFunction.parameters = parameters; + return aliasFunction; + } + + public Expr getOriginFunction() { + return originFunction; + } + + public void setOriginFunction(Expr originFunction) { + this.originFunction = originFunction; + } + + public List<String> getParameters() { + return parameters; + } + + public void setParameters(List<String> parameters) { + this.parameters = parameters; + } + + public void analyze() throws AnalysisException { + if (parameters.size() != getArgs().length) { + throw new AnalysisException("Alias function [" + functionName() + "] args number is not equal to parameters number"); + } + List<Expr> exprs = ((FunctionCallExpr) originFunction).getFnParams().exprs(); + Set<String> set = new HashSet<>(); + for (String str : parameters) { + if (!set.add(str)) { + throw new AnalysisException("Alias function [" + functionName() + "] has duplicate parameter [" + str + "]."); + } + boolean existFlag = false; + for (Expr expr : exprs) { + existFlag |= checkParams(expr, str); + } + if (!existFlag) { + throw new AnalysisException("Alias function [" + functionName() + "] do not contain parameter [" + str + "]."); + } + } + } + + private boolean checkParams(Expr expr, String parma) { + for (Expr e : expr.getChildren()) { + if (checkParams(e, parma)) { + return true; + } + } + if (expr instanceof SlotRef) { + if (parma.equals(((SlotRef) expr).getColumnName())) { + return true; + } + } + return false; + } + + @Override + public String toSql(boolean ifNotExists) { + setSlotRefLabel(originFunction); + StringBuilder sb = new StringBuilder("CREATE ALIAS FUNCTION "); + if (ifNotExists) { + sb.append("IF NOT EXISTS "); + } + sb.append(signatureString()) + .append(" WITH PARAMETER(") + .append(getParamsSting(parameters)) + .append(") AS ") + .append(originFunction.toSql()) + .append(";"); + return sb.toString(); + } + + @Override + public void write(DataOutput output) throws IOException { + // 1. type + FunctionType.ALIAS.write(output); + // 2. parent + super.writeFields(output); + // 3. parameter + output.writeInt(parameters.size()); + for (String p : parameters) { + Text.writeString(output, p); + } + // 4. expr + Expr.writeTo(originFunction, output); + } + + @Override + public void readFields(DataInput input) throws IOException { + super.readFields(input); + int counter = input.readInt(); + for (int i = 0; i < counter; i++) { + parameters.add(Text.readString(input)); + } + originFunction = Expr.readIn(input); + } + + @Override + public String getProperties() { + Map<String, String> properties = new HashMap<>(); + properties.put("parameter", getParamsSting(parameters)); + setSlotRefLabel(originFunction); + String functionStr = originFunction.toSql(); + functionStr = functionStr.replaceAll("'", "`"); + properties.put("origin_function", functionStr); + return new Gson().toJson(properties); + } + + /** + * set slotRef label to column name + * @param expr + */ + private void setSlotRefLabel(Expr expr) { + for (Expr e : expr.getChildren()) { + setSlotRefLabel(e); + } + if (expr instanceof SlotRef) { + ((SlotRef) expr).setLabel("`" + ((SlotRef) expr).getColumnName() + "`"); + } + } + + private String getParamsSting(List<String> parameters) { + return parameters.stream() + .map(String::toString) + .collect(Collectors.joining(", ")); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java index 6931b19..6013c93 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java @@ -563,7 +563,8 @@ public class Function implements Writable { enum FunctionType { ORIGIN(0), SCALAR(1), - AGGREGATE(2); + AGGREGATE(2), + ALIAS(3); private int code; @@ -582,6 +583,8 @@ public class Function implements Writable { return SCALAR; case 2: return AGGREGATE; + case 3: + return ALIAS; } return null; } @@ -652,6 +655,9 @@ public class Function implements Writable { case AGGREGATE: function = new AggregateFunction(); break; + case ALIAS: + function = new AliasFunction(); + break; default: throw new Error("Unsupported function type, type=" + functionType); } @@ -675,6 +681,9 @@ public class Function implements Writable { if (this instanceof ScalarFunction) { row.add("Scalar"); row.add("NULL"); + } else if (this instanceof AliasFunction) { + row.add("Alias"); + row.add("NULL"); } else { row.add("Aggregate"); AggregateFunction aggFunc = (AggregateFunction) this; diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java index 2abaf6b..4dacd6a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java @@ -77,6 +77,7 @@ public class FunctionSet { ScalarBuiltins.initBuiltins(this); LikePredicate.initBuiltins(this); InPredicate.initBuiltins(this); + AliasFunction.initBuiltins(this); } public void buildNonNullResultWithNullParamFunction(Set<String> funcNames) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/rewrite/RewriteAliasFunctionRule.java b/fe/fe-core/src/main/java/org/apache/doris/rewrite/RewriteAliasFunctionRule.java new file mode 100644 index 0000000..c3a0f3e --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/rewrite/RewriteAliasFunctionRule.java @@ -0,0 +1,48 @@ +// 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.rewrite; + +import org.apache.doris.analysis.Analyzer; +import org.apache.doris.analysis.Expr; +import org.apache.doris.analysis.FunctionCallExpr; +import org.apache.doris.catalog.AliasFunction; +import org.apache.doris.catalog.Function; +import org.apache.doris.common.AnalysisException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * rewrite alias function to real function + */ +public class RewriteAliasFunctionRule implements ExprRewriteRule{ + private final static Logger LOG = LogManager.getLogger(RewriteAliasFunctionRule.class); + public static RewriteAliasFunctionRule INSTANCE = new RewriteAliasFunctionRule(); + + @Override + public Expr apply(Expr expr, Analyzer analyzer) throws AnalysisException { + if (expr instanceof FunctionCallExpr) { + Function fn = expr.getFn(); + if (fn instanceof AliasFunction) { + return ((FunctionCallExpr) expr).rewriteExpr(); + } + + } + return expr; + } +} diff --git a/fe/fe-core/src/main/jflex/sql_scanner.flex b/fe/fe-core/src/main/jflex/sql_scanner.flex index bdf27b8..9a5bcc5 100644 --- a/fe/fe-core/src/main/jflex/sql_scanner.flex +++ b/fe/fe-core/src/main/jflex/sql_scanner.flex @@ -92,6 +92,7 @@ import org.apache.doris.qe.SqlModeHelper; keywordMap.put("admin", new Integer(SqlParserSymbols.KW_ADMIN)); keywordMap.put("after", new Integer(SqlParserSymbols.KW_AFTER)); keywordMap.put("aggregate", new Integer(SqlParserSymbols.KW_AGGREGATE)); + keywordMap.put("alias", new Integer(SqlParserSymbols.KW_ALIAS)); keywordMap.put("all", new Integer(SqlParserSymbols.KW_ALL)); keywordMap.put("alter", new Integer(SqlParserSymbols.KW_ALTER)); keywordMap.put("and", new Integer(SqlParserSymbols.KW_AND)); @@ -281,6 +282,7 @@ import org.apache.doris.qe.SqlModeHelper; keywordMap.put("outer", new Integer(SqlParserSymbols.KW_OUTER)); keywordMap.put("outfile", new Integer(SqlParserSymbols.KW_OUTFILE)); keywordMap.put("over", new Integer(SqlParserSymbols.KW_OVER)); + keywordMap.put("parameter", new Integer(SqlParserSymbols.KW_PARAMETER)); keywordMap.put("partition", new Integer(SqlParserSymbols.KW_PARTITION)); keywordMap.put("partitions", new Integer(SqlParserSymbols.KW_PARTITIONS)); keywordMap.put("password", new Integer(SqlParserSymbols.KW_PASSWORD)); diff --git a/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java b/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java index 00248b0..c744ef0 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java @@ -103,5 +103,28 @@ public class CreateFunctionTest { Assert.assertEquals(1, constExprLists.size()); Assert.assertEquals(1, constExprLists.get(0).size()); Assert.assertTrue(constExprLists.get(0).get(0) instanceof FunctionCallExpr); + + // create alias function + createFuncStr = "create alias function db1.id_masking(int) with parameter(id) as concat(left(id,3),'****',right(id,4));"; + createFunctionStmt = (CreateFunctionStmt) UtFrameUtils.parseAndAnalyzeStmt(createFuncStr, ctx); + Catalog.getCurrentCatalog().createFunction(createFunctionStmt); + + functions = db.getFunctions(); + Assert.assertEquals(2, functions.size()); + + queryStr = "select db1.id_masking(13888888888);"; + ctx.getState().reset(); + stmtExecutor = new StmtExecutor(ctx, queryStr); + stmtExecutor.execute(); + Assert.assertNotEquals(QueryState.MysqlStateType.ERR, ctx.getState().getStateType()); + planner = stmtExecutor.planner(); + Assert.assertEquals(1, planner.getFragments().size()); + fragment = planner.getFragments().get(0); + Assert.assertTrue(fragment.getPlanRoot() instanceof UnionNode); + unionNode = (UnionNode)fragment.getPlanRoot(); + constExprLists = Deencapsulation.getField(unionNode, "constExprLists_"); + Assert.assertEquals(1, constExprLists.size()); + Assert.assertEquals(1, constExprLists.get(0).size()); + Assert.assertTrue(constExprLists.get(0).get(0) instanceof FunctionCallExpr); } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org