[Feature] Support alias function (#6261)
Implement #6260. Add alias function type.
This commit is contained in:
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(", "));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user