[Feature] Support alias function (#6261)

Implement #6260.

Add alias function type.
This commit is contained in:
qiye
2021-08-07 21:29:13 +08:00
committed by GitHub
parent 2f5b06ae70
commit 70825ce846
19 changed files with 761 additions and 66 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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(", "));
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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);
}
}