FunctionCallExpr.java
// 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.
// This file is copied from
// https://github.com/apache/impala/blob/branch-2.9.0/fe/src/main/java/org/apache/impala/FunctionCallExpr.java
// and modified by Doris
package org.apache.doris.analysis;
import org.apache.doris.catalog.AggregateFunction;
import org.apache.doris.catalog.ArrayType;
import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.FunctionSet;
import org.apache.doris.catalog.ScalarType;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.TableIf.TableType;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.planner.normalize.Normalizer;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.thrift.TExprNode;
import org.apache.doris.thrift.TExprNodeType;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.gson.annotations.SerializedName;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
// TODO: for aggregations, we need to unify the code paths for builtins and UDAs.
public class FunctionCallExpr extends Expr {
public static final ImmutableSet<String> STDDEV_FUNCTION_SET = new ImmutableSortedSet.Builder(
String.CASE_INSENSITIVE_ORDER)
.add("stddev").add("stddev_val").add("stddev_samp").add("stddev_pop").add("variance").add("variance_pop")
.add("variance_pop").add("var_samp").add("var_pop").add("variance_samp").add("avg_weighted")
.add("std").build();
public static final Map<String, java.util.function.BiFunction<ArrayList<Expr>, Type, Type>> PRECISION_INFER_RULE;
public static final java.util.function.BiFunction<ArrayList<Expr>, Type, Type> DEFAULT_PRECISION_INFER_RULE;
public static final ImmutableSet<String> ROUND_FUNCTION_SET = new ImmutableSortedSet.Builder(
String.CASE_INSENSITIVE_ORDER)
.add("round").add("round_bankers").add("ceil").add("floor")
.add("truncate").add("dround").add("dceil").add("dfloor").build();
public static final ImmutableSet<String> STRING_SEARCH_FUNCTION_SET = new ImmutableSortedSet.Builder(
String.CASE_INSENSITIVE_ORDER)
.add("multi_search_all_positions").add("multi_match_any").build();
static {
java.util.function.BiFunction<ArrayList<Expr>, Type, Type> sumRule = (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() > 0);
if (children.get(0).getType().isDecimalV3()) {
return ScalarType.createDecimalV3Type(ScalarType.MAX_DECIMAL128_PRECISION,
((ScalarType) children.get(0).getType()).getScalarScale());
} else {
return returnType;
}
};
DEFAULT_PRECISION_INFER_RULE = (children, returnType) -> {
if (children != null && children.size() > 0
&& children.get(0).getType().isDecimalV3() && returnType.isDecimalV3()) {
return children.get(0).getType();
} else if (children != null && children.size() > 0 && children.get(0).getType().isDatetimeV2()
&& returnType.isDatetimeV2()) {
return children.get(0).getType();
} else if (children != null && children.size() > 0 && children.get(0).getType().isDecimalV2()
&& returnType.isDecimalV2()) {
return children.get(0).getType();
} else {
return returnType;
}
};
java.util.function.BiFunction<ArrayList<Expr>, Type, Type> roundRule = (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() > 0);
if (children.size() == 1 && children.get(0).getType().isDecimalV3()) {
return ScalarType.createDecimalV3Type(children.get(0).getType().getPrecision(), 0);
} else if (children.size() == 2) {
Expr scaleExpr = children.get(1);
if (scaleExpr instanceof IntLiteral
|| (scaleExpr instanceof CastExpr && scaleExpr.getChild(0) instanceof IntLiteral)) {
if (children.get(1) instanceof CastExpr && children.get(1).getChild(0) instanceof IntLiteral) {
children.get(1).getChild(0).setType(children.get(1).getType());
children.set(1, children.get(1).getChild(0));
} else {
children.get(1).setType(Type.INT);
}
int scaleArg = (int) (((IntLiteral) children.get(1)).getValue());
return ScalarType.createDecimalV3Type(children.get(0).getType().getPrecision(),
Math.min(Math.max(scaleArg, 0), ((ScalarType) children.get(0).getType()).decimalScale()));
} else {
// Scale argument is a Column, always use same scale with input decimal
return ScalarType.createDecimalV3Type(children.get(0).getType().getPrecision(),
((ScalarType) children.get(0).getType()).decimalScale());
}
} else {
return returnType;
}
};
java.util.function.BiFunction<ArrayList<Expr>, Type, Type> arrayDateTimeV2OrDecimalV3Rule
= (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() > 0);
if (children.get(0).getType().isArrayType() && (
((ArrayType) children.get(0).getType()).getItemType().isDecimalV3()
|| ((ArrayType) children.get(0)
.getType()).getItemType().isDecimalV2() || ((ArrayType) children.get(0)
.getType()).getItemType().isDatetimeV2())) {
return ((ArrayType) children.get(0).getType()).getItemType();
} else {
return returnType;
}
};
java.util.function.BiFunction<ArrayList<Expr>, Type, Type> arrayDecimal128Rule
= (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() > 0);
if (children.get(0).getType().isArrayType() && (
((ArrayType) children.get(0).getType()).getItemType().isDecimalV3())) {
return ScalarType.createDecimalV3Type(ScalarType.MAX_DECIMAL128_PRECISION,
((ScalarType) ((ArrayType) children.get(0).getType()).getItemType()).getScalarScale());
} else {
return returnType;
}
};
java.util.function.BiFunction<ArrayList<Expr>, Type, Type> arrayDecimal128ArrayRule
= (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() > 0);
if (children.get(0).getType().isArrayType() && (
((ArrayType) children.get(0).getType()).getItemType().isDecimalV3())) {
ArrayType childArrayType = (ArrayType) children.get(0).getType();
Type itemType = ScalarType.createDecimalV3Type(ScalarType.MAX_DECIMAL128_PRECISION,
((ScalarType) childArrayType.getItemType()).getScalarScale());
return ArrayType.create(itemType, childArrayType.getContainsNull());
} else {
return returnType;
}
};
PRECISION_INFER_RULE = new HashMap<>();
PRECISION_INFER_RULE.put("sum", sumRule);
PRECISION_INFER_RULE.put("multi_distinct_sum", sumRule);
PRECISION_INFER_RULE.put("avg", (children, returnType) -> {
// TODO: how to set scale?
Preconditions.checkArgument(children != null && children.size() > 0);
if (children.get(0).getType().isDecimalV3()) {
return ScalarType.createDecimalV3Type(ScalarType.MAX_DECIMAL128_PRECISION,
Math.max(((ScalarType) children.get(0).getType()).getScalarScale(), 4));
} else {
return returnType;
}
});
PRECISION_INFER_RULE.put("if", (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() == 3);
if (children.get(1).getType().isDecimalV3() && children.get(2).getType().isDecimalV3()) {
return Expr.getAssignmentCompatibleType(children.subList(1, children.size()));
} else if (children.get(1).getType().isDatetimeV2() && children.get(2).getType().isDatetimeV2()) {
return Expr.getAssignmentCompatibleType(children.subList(1, children.size()));
} else {
return returnType;
}
});
PRECISION_INFER_RULE.put("ifnull", (children, returnType) -> {
Preconditions.checkArgument(children != null && children.size() == 2);
if (children.get(0).getType().isDecimalV3() && children.get(1).getType().isDecimalV3()) {
return Expr.getAssignmentCompatibleType(children);
} else if (children.get(0).getType().isDatetimeV2() && children.get(1).getType().isDatetimeV2()) {
return Expr.getAssignmentCompatibleType(children);
} else {
return returnType;
}
});
PRECISION_INFER_RULE.put("coalesce", (children, returnType) -> {
boolean isDecimalV3 = true;
boolean isDateTimeV2 = true;
Type assignmentCompatibleType = Expr.getAssignmentCompatibleType(children);
for (Expr child : children) {
isDecimalV3 = isDecimalV3 && child.getType().isDecimalV3();
isDateTimeV2 = isDateTimeV2 && child.getType().isDatetimeV2();
}
if ((isDecimalV3 || isDateTimeV2) && assignmentCompatibleType.isValid()) {
return assignmentCompatibleType;
} else {
return returnType;
}
});
PRECISION_INFER_RULE.put("array_min", arrayDateTimeV2OrDecimalV3Rule);
PRECISION_INFER_RULE.put("array_max", arrayDateTimeV2OrDecimalV3Rule);
PRECISION_INFER_RULE.put("element_at", arrayDateTimeV2OrDecimalV3Rule);
PRECISION_INFER_RULE.put("%element_extract%", arrayDateTimeV2OrDecimalV3Rule);
PRECISION_INFER_RULE.put("array_avg", arrayDecimal128Rule);
PRECISION_INFER_RULE.put("array_sum", arrayDecimal128Rule);
PRECISION_INFER_RULE.put("array_product", arrayDecimal128Rule);
PRECISION_INFER_RULE.put("array_cum_sum", arrayDecimal128ArrayRule);
PRECISION_INFER_RULE.put("round", roundRule);
PRECISION_INFER_RULE.put("round_bankers", roundRule);
PRECISION_INFER_RULE.put("ceil", roundRule);
PRECISION_INFER_RULE.put("floor", roundRule);
PRECISION_INFER_RULE.put("dround", roundRule);
PRECISION_INFER_RULE.put("dceil", roundRule);
PRECISION_INFER_RULE.put("dfloor", roundRule);
PRECISION_INFER_RULE.put("truncate", roundRule);
}
public static final ImmutableSet<String> TIME_FUNCTIONS_WITH_PRECISION = new ImmutableSortedSet.Builder(
String.CASE_INSENSITIVE_ORDER)
.add("now").add("current_timestamp").add("localtime").add("localtimestamp").build();
public static final int STDDEV_DECIMAL_SCALE = 9;
private static final String ELEMENT_EXTRACT_FN_NAME = "%element_extract%";
private static final Logger LOG = LogManager.getLogger(FunctionCallExpr.class);
@SerializedName("fnn")
private FunctionName fnName;
@SerializedName("fnp")
private FunctionParams fnParams;
private FunctionParams aggFnParams;
private List<OrderByElement> orderByElements = Lists.newArrayList();
// check analytic function
@SerializedName("iafc")
private boolean isAnalyticFnCall = false;
// check table function
@SerializedName("itfc")
private boolean isTableFnCall = false;
// Indicates whether this is a merge aggregation function that should use the
// merge
// instead of the update symbol. This flag also affects the behavior of
// resetAnalysisState() which is used during expr substitution.
private boolean isMergeAggFn;
// use to record the num of json_object parameters
private int originChildSize;
// Save the functionCallExpr in the original statement
private Expr originStmtFnExpr;
private boolean isRewrote = false;
// this field is set by nereids, so we would not get arg types by the children.
private Optional<List<Type>> argTypesForNereids = Optional.empty();
public void setAggFnParams(FunctionParams aggFnParams) {
this.aggFnParams = aggFnParams;
}
public FunctionParams getAggFnParams() {
return aggFnParams;
}
public void setIsAnalyticFnCall(boolean v) {
isAnalyticFnCall = v;
}
public void setTableFnCall(boolean tableFnCall) {
isTableFnCall = tableFnCall;
}
public void setFnName(FunctionName fnName) {
this.fnName = fnName;
}
public Function getFn() {
return fn;
}
public FunctionName getFnName() {
return fnName;
}
public FunctionParams getFnParams() {
return fnParams;
}
// only used restore from readFields.
protected FunctionCallExpr() {
super();
}
@Override
public String getExprName() {
if (!this.exprName.isPresent()) {
this.exprName = Optional.of(Utils.normalizeName(this.getFnName().getFunction(), DEFAULT_EXPR_NAME));
}
return this.exprName.get();
}
public FunctionCallExpr(String functionName, List<Expr> params) {
this(new FunctionName(functionName), new FunctionParams(false, params));
}
public FunctionCallExpr(FunctionName fnName, List<Expr> params) {
this(fnName, new FunctionParams(false, params));
}
public FunctionCallExpr(FunctionName fnName, List<Expr> params, List<OrderByElement> orderByElements)
throws AnalysisException {
this(fnName, new FunctionParams(false, params), orderByElements);
}
public FunctionCallExpr(String fnName, FunctionParams params) {
this(new FunctionName(fnName), params, false);
}
public FunctionCallExpr(FunctionName fnName, FunctionParams params) {
this(fnName, params, false);
}
public FunctionCallExpr(
FunctionName fnName, FunctionParams params, List<OrderByElement> orderByElements) throws AnalysisException {
this(fnName, params, false);
this.orderByElements = orderByElements;
if (!orderByElements.isEmpty()) {
if (!AggregateFunction.SUPPORT_ORDER_BY_AGGREGATE_FUNCTION_NAME_SET
.contains(fnName.getFunction().toLowerCase())) {
throw new AnalysisException(
"ORDER BY not support for the function:" + fnName.getFunction().toLowerCase());
}
}
setChildren();
originChildSize = children.size();
}
private FunctionCallExpr(
FunctionName fnName, FunctionParams params, boolean isMergeAggFn) {
super();
this.fnName = fnName;
fnParams = params;
this.isMergeAggFn = isMergeAggFn;
if (params.exprs() != null) {
children.addAll(params.exprs());
}
originChildSize = children.size();
}
public FunctionCallExpr(String functionName, FunctionParams params, FunctionParams aggFnParams,
Optional<List<Type>> argTypes) {
this.fnName = new FunctionName(functionName);
this.fnParams = params;
this.isMergeAggFn = false;
this.aggFnParams = aggFnParams;
if (fnParams.exprs() != null) {
children.addAll(fnParams.exprs());
}
this.originChildSize = children.size();
this.argTypesForNereids = argTypes;
}
// nereids scalar function call expr constructor without finalize/analyze
public FunctionCallExpr(Function function, FunctionParams functionParams) {
this(function, functionParams, null, false, functionParams.exprs());
}
// nereids aggregate function call expr constructor without finalize/analyze
public FunctionCallExpr(Function function, FunctionParams functionParams, FunctionParams aggFnParams,
boolean isMergeAggFn, List<Expr> children) {
this.fnName = function.getFunctionName();
this.fn = function;
this.type = function.getReturnType();
this.fnParams = functionParams;
this.aggFnParams = aggFnParams;
this.children.addAll(children);
this.originChildSize = children.size();
this.isMergeAggFn = isMergeAggFn;
}
// Constructs the same agg function with new params.
public FunctionCallExpr(FunctionCallExpr e, FunctionParams params) {
Preconditions.checkState(e.isAnalyzed);
Preconditions.checkState(e.isAggregateFunction() || e.isAnalyticFnCall);
fnName = e.fnName;
// aggOp = e.aggOp;
isAnalyticFnCall = e.isAnalyticFnCall;
fnParams = params;
aggFnParams = e.aggFnParams;
// Just inherit the function object from 'e'.
fn = e.fn;
this.isMergeAggFn = e.isMergeAggFn;
if (params.exprs() != null) {
children.addAll(params.exprs());
}
this.originChildSize = children.size();
}
protected FunctionCallExpr(FunctionCallExpr other) {
super(other);
fnName = other.fnName != null ? other.fnName.clone() : null;
orderByElements = other.orderByElements;
isAnalyticFnCall = other.isAnalyticFnCall;
// aggOp = other.aggOp;
// fnParams = other.fnParams;
// Clone the params in a way that keeps the children_ and the params.exprs()
// in sync. The children have already been cloned in the super c'tor.
fnParams = other.fnParams.clone(children);
originChildSize = other.originChildSize;
aggFnParams = other.aggFnParams;
this.isMergeAggFn = other.isMergeAggFn;
fn = other.fn;
this.isTableFnCall = other.isTableFnCall;
}
public static int computeJsonDataType(Type type) {
if (type.isNull()) {
return 0;
} else if (type.isBoolean()) {
return 1;
} else if (type.isFixedPointType()) {
if (type.isInteger32Type()) {
return 2;
} else {
return 5;
}
} else if (type.isFloatingPointType() || type.isDecimalV2() || type.isDecimalV3()) {
return 3;
} else if (type.isTime()) {
return 4;
} else if (type.isComplexType() || type.isJsonbType()) {
return 7;
} else {
// default is string for BE execution
return 6;
}
}
public boolean isMergeAggFn() {
return isMergeAggFn;
}
@Override
public Expr clone() {
return new FunctionCallExpr(this);
}
@Override
public void resetAnalysisState() {
isAnalyzed = false;
// Resolving merge agg functions after substitution may fail e.g., if the
// intermediate agg type is not the same as the output type. Preserve the original
// fn_ such that analyze() hits the special-case code for merge agg fns that
// handles this case.
if (!isMergeAggFn) {
fn = null;
}
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
FunctionCallExpr o = (FunctionCallExpr) obj;
if (orderByElements == null ^ o.orderByElements == null) {
return false;
}
if (orderByElements != null) {
if (orderByElements.size() != o.orderByElements.size()) {
return false;
}
for (int i = 0; i < orderByElements.size(); i++) {
if (!orderByElements.get(i).equals(o.orderByElements.get(i))) {
return false;
}
}
}
return /*opcode == o.opcode && aggOp == o.aggOp &&*/ fnName.equals(o.fnName)
&& fnParams.isDistinct() == o.fnParams.isDistinct()
&& fnParams.isStar() == o.fnParams.isStar();
}
private String paramsToSql() {
StringBuilder sb = new StringBuilder();
sb.append("(");
if (fnParams.isStar()) {
sb.append("*");
}
if (fnParams.isDistinct()) {
sb.append("DISTINCT ");
}
int len = children.size();
if (fnName.getFunction().equalsIgnoreCase("char")) {
for (int i = 1; i < len; ++i) {
sb.append(children.get(i).toSql());
if (i < len - 1) {
sb.append(", ");
}
}
sb.append(" using ");
String encodeType = children.get(0).toSql();
if (encodeType.charAt(0) == '\'') {
encodeType = encodeType.substring(1, encodeType.length());
}
if (encodeType.charAt(encodeType.length() - 1) == '\'') {
encodeType = encodeType.substring(0, encodeType.length() - 1);
}
sb.append(encodeType).append(")");
return sb.toString();
}
// XXX_diff are used by nereids only
if (fnName.getFunction().equalsIgnoreCase("years_diff") || fnName.getFunction().equalsIgnoreCase("months_diff")
|| fnName.getFunction().equalsIgnoreCase("days_diff")
|| fnName.getFunction().equalsIgnoreCase("hours_diff")
|| fnName.getFunction().equalsIgnoreCase("minutes_diff")
|| fnName.getFunction().equalsIgnoreCase("seconds_diff")
|| fnName.getFunction().equalsIgnoreCase("milliseconds_diff")
|| fnName.getFunction().equalsIgnoreCase("microseconds_diff")) {
sb.append(children.get(0).toSql()).append(", ");
sb.append(children.get(1).toSql()).append(")");
return sb.toString();
}
// used by nereids END
for (int i = 0; i < len; ++i) {
if (i != 0) {
if (fnName.getFunction().equalsIgnoreCase("group_concat")
&& orderByElements.size() > 0 && i == len - orderByElements.size()) {
sb.append(" ");
} else {
sb.append(", ");
}
}
if (ConnectContext.get() != null && ConnectContext.get().getState().isQuery() && i == 1
&& (fnName.getFunction().equalsIgnoreCase("aes_decrypt")
|| fnName.getFunction().equalsIgnoreCase("aes_encrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_decrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_encrypt"))) {
sb.append("\'***\'");
continue;
} else if (orderByElements.size() > 0 && i == len - orderByElements.size()) {
sb.append("ORDER BY ");
}
sb.append(children.get(i).toSql());
if (orderByElements.size() > 0 && i >= len - orderByElements.size()) {
if (orderByElements.get(i - len + orderByElements.size()).getIsAsc()) {
sb.append(" ASC");
} else {
sb.append(" DESC");
}
}
}
sb.append(")");
return sb.toString();
}
private String paramsToSql(boolean disableTableName, boolean needExternalSql, TableType tableType,
TableIf table) {
StringBuilder sb = new StringBuilder();
sb.append("(");
if (fnParams.isStar()) {
sb.append("*");
}
if (fnParams.isDistinct()) {
sb.append("DISTINCT ");
}
int len = children.size();
if (fnName.getFunction().equalsIgnoreCase("char")) {
for (int i = 1; i < len; ++i) {
sb.append(children.get(i).toSql(disableTableName, needExternalSql, tableType, table));
if (i < len - 1) {
sb.append(", ");
}
}
sb.append(" using ");
String encodeType = children.get(0).toSql(disableTableName, needExternalSql, tableType, table);
if (encodeType.charAt(0) == '\'') {
encodeType = encodeType.substring(1, encodeType.length());
}
if (encodeType.charAt(encodeType.length() - 1) == '\'') {
encodeType = encodeType.substring(0, encodeType.length() - 1);
}
sb.append(encodeType).append(")");
return sb.toString();
}
// XXX_diff are used by nereids only
if (fnName.getFunction().equalsIgnoreCase("years_diff") || fnName.getFunction().equalsIgnoreCase("months_diff")
|| fnName.getFunction().equalsIgnoreCase("days_diff")
|| fnName.getFunction().equalsIgnoreCase("hours_diff")
|| fnName.getFunction().equalsIgnoreCase("minutes_diff")
|| fnName.getFunction().equalsIgnoreCase("seconds_diff")
|| fnName.getFunction().equalsIgnoreCase("milliseconds_diff")
|| fnName.getFunction().equalsIgnoreCase("microseconds_diff")) {
sb.append(children.get(0).toSql(disableTableName, needExternalSql, tableType, table)).append(", ");
sb.append(children.get(1).toSql(disableTableName, needExternalSql, tableType, table)).append(")");
return sb.toString();
}
for (int i = 0; i < len; ++i) {
if (i != 0) {
if (fnName.getFunction().equalsIgnoreCase("group_concat")
&& orderByElements.size() > 0 && i == len - orderByElements.size()) {
sb.append(" ");
} else {
sb.append(", ");
}
}
if (ConnectContext.get() != null && ConnectContext.get().getState().isQuery() && i == 1
&& (fnName.getFunction().equalsIgnoreCase("aes_decrypt")
|| fnName.getFunction().equalsIgnoreCase("aes_encrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_decrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_encrypt"))) {
sb.append("\'***\'");
continue;
} else if (orderByElements.size() > 0 && i == len - orderByElements.size()) {
sb.append("ORDER BY ");
}
sb.append(children.get(i).toSql(disableTableName, needExternalSql, tableType, table));
if (orderByElements.size() > 0 && i >= len - orderByElements.size()) {
if (orderByElements.get(i - len + orderByElements.size()).getIsAsc()) {
sb.append(" ASC");
} else {
sb.append(" DESC");
}
}
}
sb.append(")");
return sb.toString();
}
@Override
public String toSqlImpl() {
Expr expr;
if (originStmtFnExpr != null) {
expr = originStmtFnExpr;
} else {
expr = this;
}
StringBuilder sb = new StringBuilder();
// when function is like or regexp, the expr generated sql should be like this
// eg: child1 like child2
if (fnName.getFunction().equalsIgnoreCase("like")
|| fnName.getFunction().equalsIgnoreCase("regexp")) {
sb.append(children.get(0).toSql());
sb.append(" ");
sb.append(((FunctionCallExpr) expr).fnName);
sb.append(" ");
sb.append(children.get(1).toSql());
} else if (fnName.getFunction().equalsIgnoreCase("encryptkeyref")) {
sb.append("key ");
for (int i = 0; i < children.size(); i++) {
String str = ((StringLiteral) children.get(i)).getValue();
if (str.isEmpty()) {
continue;
}
sb.append(str);
sb.append(".");
}
sb.deleteCharAt(sb.length() - 1);
} else {
sb.append(((FunctionCallExpr) expr).fnName);
sb.append(paramsToSql());
if (fnName.getFunction().equalsIgnoreCase("json_quote")
|| fnName.getFunction().equalsIgnoreCase("json_array")
|| fnName.getFunction().equalsIgnoreCase("json_object")
|| fnName.getFunction().equalsIgnoreCase("json_insert")
|| fnName.getFunction().equalsIgnoreCase("json_replace")
|| fnName.getFunction().equalsIgnoreCase("json_set")) {
return forJSON(sb.toString());
}
}
return sb.toString();
}
@Override
public String toSqlImpl(boolean disableTableName, boolean needExternalSql, TableType tableType,
TableIf table) {
Expr expr;
if (originStmtFnExpr != null) {
expr = originStmtFnExpr;
} else {
expr = this;
}
StringBuilder sb = new StringBuilder();
// when function is like or regexp, the expr generated sql should be like this
// eg: child1 like child2
if (fnName.getFunction().equalsIgnoreCase("like")
|| fnName.getFunction().equalsIgnoreCase("regexp")) {
sb.append(children.get(0).toSql(disableTableName, needExternalSql, tableType, table));
sb.append(" ");
sb.append(((FunctionCallExpr) expr).fnName);
sb.append(" ");
sb.append(children.get(1).toSql(disableTableName, needExternalSql, tableType, table));
} else if (fnName.getFunction().equalsIgnoreCase("encryptkeyref")) {
sb.append("key ");
for (int i = 0; i < children.size(); i++) {
String str = ((StringLiteral) children.get(i)).getValue();
if (str.isEmpty()) {
continue;
}
sb.append(str);
sb.append(".");
}
sb.deleteCharAt(sb.length() - 1);
} else {
sb.append(((FunctionCallExpr) expr).fnName);
sb.append(paramsToSql(disableTableName, needExternalSql, tableType, table));
if (fnName.getFunction().equalsIgnoreCase("json_quote")
|| fnName.getFunction().equalsIgnoreCase("json_array")
|| fnName.getFunction().equalsIgnoreCase("json_object")
|| fnName.getFunction().equalsIgnoreCase("json_insert")
|| fnName.getFunction().equalsIgnoreCase("json_replace")
|| fnName.getFunction().equalsIgnoreCase("json_set")) {
return forJSON(sb.toString());
}
}
return sb.toString();
}
private String paramsToDigest() {
StringBuilder sb = new StringBuilder();
sb.append("(");
if (fnParams.isStar()) {
sb.append("*");
}
if (fnParams.isDistinct()) {
sb.append("DISTINCT ");
}
int len = children.size();
List<String> result = Lists.newArrayList();
if (fnName.getFunction().equalsIgnoreCase("aes_decrypt")
|| fnName.getFunction().equalsIgnoreCase("aes_encrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_decrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_encrypt")) {
len = len - 1;
}
for (int i = 0; i < len; ++i) {
if (i == 1 && (fnName.getFunction().equalsIgnoreCase("aes_decrypt")
|| fnName.getFunction().equalsIgnoreCase("aes_encrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_decrypt"))) {
result.add("\'***\'");
} else {
result.add(children.get(i).toDigest());
}
}
sb.append(Joiner.on(", ").join(result)).append(")");
return sb.toString();
}
@Override
public String toDigestImpl() {
Expr expr;
if (originStmtFnExpr != null) {
expr = originStmtFnExpr;
} else {
expr = this;
}
StringBuilder sb = new StringBuilder();
sb.append(((FunctionCallExpr) expr).fnName);
sb.append(paramsToDigest());
if (fnName.getFunction().equalsIgnoreCase("json_quote")
|| fnName.getFunction().equalsIgnoreCase("json_array")
|| fnName.getFunction().equalsIgnoreCase("json_object")
|| fnName.getFunction().equalsIgnoreCase("json_insert")
|| fnName.getFunction().equalsIgnoreCase("json_replace")
|| fnName.getFunction().equalsIgnoreCase("json_set")) {
return forJSON(sb.toString());
}
return sb.toString();
}
@Override
public String debugString() {
return MoreObjects.toStringHelper(this)/*.add("op", aggOp)*/.add("name", fnName).add("isStar",
fnParams.isStar()).add("isDistinct", fnParams.isDistinct()).addValue(
super.debugString()).toString();
}
public FunctionParams getParams() {
return fnParams;
}
public boolean isAggregateFunction() {
Preconditions.checkState(fn != null);
return fn instanceof AggregateFunction && !isAnalyticFnCall;
}
public boolean isDistinct() {
Preconditions.checkState(isAggregateFunction());
return fnParams.isDistinct();
}
@Override
protected void toThrift(TExprNode msg) {
// TODO: we never serialize this to thrift if it's an aggregate function
// except in test cases that do it explicitly.
if (isAggregate() || isAnalyticFnCall) {
msg.node_type = TExprNodeType.AGG_EXPR;
if (aggFnParams == null) {
aggFnParams = fnParams;
}
msg.setAggExpr(aggFnParams.createTAggregateExpr(isMergeAggFn));
} else {
msg.node_type = TExprNodeType.FUNCTION_CALL;
}
}
/**
* This analyzeImp used for DefaultValueExprDef
* to generate a builtinFunction.
*
* @throws AnalysisException
*/
public void analyzeImplForDefaultValue(Type type) throws AnalysisException {
Type[] childTypes = new Type[children.size()];
for (int i = 0; i < children.size(); i++) {
childTypes[i] = children.get(i).type;
}
fn = new Function(
getBuiltinFunction(fnName.getFunction(), childTypes, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF));
fn.setReturnType(type);
this.type = type;
for (int i = 0; i < children.size(); ++i) {
if (getChild(i).getType().isNull()) {
uncheckedCastChild(Type.BOOLEAN, i);
}
}
}
private static boolean match(String pattern, int pos, String value) {
int length = value.length();
int end = pattern.length();
return pos + length <= end && pattern.substring(pos, pos + length).equals(value);
}
private static int parseNumber(String s) {
String[] n = s.split(""); // array of strings
int num = 0;
for (String value : n) {
// validating numbers
if ((value.matches("[0-9]+"))) {
num++;
} else {
return num;
}
}
return num;
}
public static boolean parsePattern(String pattern) {
int pos = 0;
int len = pattern.length();
while (pos < len) {
if (match(pattern, pos, "(?")) {
pos += 2;
if (match(pattern, pos, "t")) {
pos += 1;
if (match(pattern, pos, "<=") || match(pattern, pos, "==")
|| match(pattern, pos, ">=")) {
pos += 2;
} else if (match(pattern, pos, ">") || match(pattern, pos, "<")) {
pos += 1;
} else {
return false;
}
int numLen = parseNumber(pattern.substring(pos));
if (numLen == 0) {
return false;
} else {
pos += numLen;
}
} else {
int numLen = parseNumber(pattern.substring(pos));
if (numLen == 0) {
return false;
} else {
pos += numLen;
}
}
if (!match(pattern, pos, ")")) {
return false;
}
pos += 1;
} else if (match(pattern, pos, ".*")) {
pos += 2;
} else if (match(pattern, pos, ".")) {
pos += 1;
} else {
return false;
}
}
return true;
}
@Override
protected void normalize(TExprNode msg, Normalizer normalizer) {
String functionName = fnName.getFunction().toUpperCase();
if (FunctionSet.nonDeterministicFunctions.contains(functionName)
|| "NOW".equals(functionName)
|| (FunctionSet.nonDeterministicTimeFunctions.contains(functionName) && children.isEmpty())) {
throw new IllegalStateException("Can not normalize non deterministic functions");
}
super.normalize(msg, normalizer);
}
@Override
protected boolean isConstantImpl() {
// TODO: we can't correctly determine const-ness before analyzing 'fn_'. We
// should
// rework logic so that we do not call this function on unanalyzed exprs.
// Aggregate functions are never constant.
if (fn instanceof AggregateFunction || fn == null) {
return false;
}
final String fnName = this.fnName.getFunction();
// Non-deterministic functions are never constant.
if (isNondeterministicBuiltinFnName(fnName)) {
return false;
}
// Sleep is a special function for testing.
if (fnName.equalsIgnoreCase("sleep")) {
return false;
}
return super.isConstantImpl();
}
private static boolean isNondeterministicBuiltinFnName(String fnName) {
if (fnName.equalsIgnoreCase("rand") || fnName.equalsIgnoreCase("random")
|| fnName.equalsIgnoreCase("uuid")) {
return true;
}
return false;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Objects.hashCode(opcode);
result = 31 * result + Objects.hashCode(fnName);
return result;
}
public String forJSON(String str) {
final StringBuilder result = new StringBuilder();
StringCharacterIterator iterator = new StringCharacterIterator(str);
char character = iterator.current();
while (character != StringCharacterIterator.DONE) {
if (character == '\"') {
result.append("\\\"");
} else if (character == '\\') {
result.append("\\\\");
} else if (character == '/') {
result.append("\\/");
} else if (character == '\b') {
result.append("\\b");
} else if (character == '\f') {
result.append("\\f");
} else if (character == '\n') {
result.append("\\n");
} else if (character == '\r') {
result.append("\\r");
} else if (character == '\t') {
result.append("\\t");
} else {
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
public List<OrderByElement> getOrderByElements() {
return orderByElements;
}
public void setOrderByElements(List<OrderByElement> orderByElements) {
this.orderByElements = orderByElements;
}
private void setChildren() {
orderByElements.forEach(o -> addChild(o.getExpr()));
}
// eg: date_floor("0001-01-01 00:00:18",interval 5 second) convert to
// second_floor("0001-01-01 00:00:18", 5, "0001-01-01 00:00:00");
public static FunctionCallExpr functionWithIntervalConvert(String functionName, Expr str, Expr interval,
String timeUnitIdent) throws AnalysisException {
String newFunctionName = timeUnitIdent + "_" + functionName.split("_")[1];
List<Expr> params = new ArrayList<>();
Expr defaultDatetime = new DateLiteral(0001, 01, 01, 0, 0, 0, 0, Type.DATETIMEV2);
params.add(str);
params.add(interval);
params.add(defaultDatetime);
return new FunctionCallExpr(newFunctionName, params);
}
}