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.AliasFunction;
import org.apache.doris.catalog.ArrayType;
import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.DatabaseIf;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.FunctionSet;
import org.apache.doris.catalog.FunctionUtil;
import org.apache.doris.catalog.MapType;
import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.catalog.ScalarFunction;
import org.apache.doris.catalog.ScalarType;
import org.apache.doris.catalog.StructField;
import org.apache.doris.catalog.StructType;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.Config;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.datasource.InternalCatalog;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.planner.normalize.Normalizer;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.SessionVariable;
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.base.Strings;
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.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.DataInput;
import java.io.IOException;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
// 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").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 String parseJsonValueModifyDataType() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < children.size(); ++i) {
Type type = getChild(i).getType();
if (i > 0 && (i & 1) == 0 && type.isNull()) {
children.set(i, new StringLiteral("NULL"));
}
sb.append(computeJsonDataType(type));
}
return sb.toString();
}
public String parseJsonDataType(boolean useKeyCheck) throws AnalysisException {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < children.size(); ++i) {
Type type = getChild(i).getType();
if (type.isNull()) { // Not to return NULL directly, so save string, but flag is '0'
if (((i & 1) == 0) && useKeyCheck == true) {
throw new AnalysisException("json_object key can't be NULL: " + this.toSql());
}
children.set(i, new StringLiteral("NULL"));
}
sb.append(computeJsonDataType(type));
}
return sb.toString();
}
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
protected Expr substituteImpl(ExprSubstitutionMap smap, ExprSubstitutionMap disjunctsMap, Analyzer analyzer) {
if (aggFnParams != null && aggFnParams.exprs() != null) {
ArrayList<Expr> newParams = new ArrayList<Expr>();
for (Expr expr : aggFnParams.exprs()) {
Expr substExpr = smap.get(expr);
if (substExpr != null) {
newParams.add(substExpr.clone());
} else {
newParams.add(expr);
}
}
aggFnParams = aggFnParams
.clone(newParams);
}
if (isImplicitCast()) {
return getChild(0).substituteImpl(smap, disjunctsMap, analyzer);
}
if (smap != null) {
Expr substExpr = smap.get(this);
if (substExpr != null) {
return substExpr.clone();
}
}
if (Expr.IS_OR_PREDICATE.apply(this) && disjunctsMap != null) {
smap = disjunctsMap;
disjunctsMap = null;
}
for (int i = 0; i < children.size(); ++i) {
// we shouldn't change literal expr in function call expr
if (!(children.get(i) instanceof LiteralExpr)) {
children.set(i, children.get(i).substituteImpl(smap, disjunctsMap, analyzer));
}
}
// SlotRefs must remain analyzed to support substitution across query blocks. All
// other exprs must be analyzed again after the substitution to add implicit casts
// and for resolving their correct function signature.
resetAnalysisState();
return this;
}
@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
if (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")) {
len = len - 1;
}
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();
}
@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();
}
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("json_array")
|| fnName.getFunction().equalsIgnoreCase("json_object")
|| fnName.getFunction().equalsIgnoreCase("json_insert")
|| fnName.getFunction().equalsIgnoreCase("json_replace")
|| fnName.getFunction().equalsIgnoreCase("json_set")) {
len = len - 1;
}
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 isScalarFunction() {
Preconditions.checkState(fn != null);
return fn instanceof ScalarFunction;
}
public boolean isAggregateFunction() {
Preconditions.checkState(fn != null);
return fn instanceof AggregateFunction && !isAnalyticFnCall;
}
/**
* Returns true if this is a call to an aggregate function that returns
* non-null on an empty input (e.g. count).
*/
public boolean returnsNonNullOnEmpty() {
Preconditions.checkNotNull(fn);
return fn instanceof AggregateFunction
&& ((AggregateFunction) fn).returnsNonNullOnEmpty();
}
public boolean isDistinct() {
Preconditions.checkState(isAggregateFunction());
return fnParams.isDistinct();
}
public boolean isCountDistinctBitmapOrHLL() {
if (!fnParams.isDistinct()) {
return false;
}
if (!fnName.getFunction().equalsIgnoreCase(FunctionSet.COUNT)) {
return false;
}
if (children.size() != 1) {
return false;
}
Type type = getChild(0).getType();
return type.isBitmapType() || type.isHllType();
}
@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;
}
}
private void analyzeBuiltinAggFunction(Analyzer analyzer) throws AnalysisException {
if (fnParams.isStar() && !fnName.getFunction().equalsIgnoreCase(FunctionSet.COUNT)) {
throw new AnalysisException(
"'*' can only be used in conjunction with COUNT: " + this.toSql());
}
if (fnName.getFunction().equalsIgnoreCase(FunctionSet.COUNT)) {
// for multiple exprs count must be qualified with distinct
if (children.size() > 1 && !fnParams.isDistinct()) {
throw new AnalysisException(
"COUNT must have DISTINCT for multiple arguments: " + this.toSql());
}
return;
}
if (fnName.getFunction().equalsIgnoreCase("json_array")) {
String res = parseJsonDataType(false);
if (children.size() == originChildSize) {
children.add(new StringLiteral(res));
}
return;
}
if (fnName.getFunction().equalsIgnoreCase("json_object")) {
if ((children.size() & 1) == 1 && (originChildSize == children.size())) {
throw new AnalysisException("json_object can't be odd parameters, need even parameters: "
+ this.toSql());
}
String res = parseJsonDataType(true);
if (children.size() == originChildSize) {
children.add(new StringLiteral(res));
}
return;
}
if (fnName.getFunction().equalsIgnoreCase("json_insert")
|| fnName.getFunction().equalsIgnoreCase("json_replace")
|| fnName.getFunction().equalsIgnoreCase("json_set")) {
if (((children.size() & 1) == 0 || children.size() < 3)
&& (originChildSize == children.size())) {
throw new AnalysisException(fnName.getFunction() + " need odd parameters, and >= 3 arguments: "
+ this.toSql());
}
String res = parseJsonValueModifyDataType();
if (children.size() == originChildSize) {
children.add(new StringLiteral(res));
}
return;
}
if (fnName.getFunction().equalsIgnoreCase("group_concat")) {
if (children.size() - orderByElements.size() > 2 || children.isEmpty()) {
throw new AnalysisException(
"group_concat requires one or two parameters: " + this.toSql());
}
Expr arg0 = getChild(0);
if (!arg0.type.isStringType() && !arg0.type.isNull()) {
throw new AnalysisException(
"group_concat requires first parameter to be of type STRING: " + this.toSql());
}
if (children.size() - orderByElements.size() == 2) {
Expr arg1 = getChild(1);
if (!arg1.type.isStringType() && !arg1.type.isNull()) {
throw new AnalysisException(
"group_concat requires second parameter to be of type STRING: " + this.toSql());
}
}
if (fnParams.isDistinct() && !orderByElements.isEmpty()) {
throw new AnalysisException(
"group_concat don't support using distinct with order by together: " + this.toSql());
}
return;
}
if (fnName.getFunction().equalsIgnoreCase("field")) {
if (children.size() < 2) {
throw new AnalysisException(fnName.getFunction() + " function parameter size is less than 2.");
} else {
for (int i = 1; i < children.size(); ++i) {
if (!getChild(i).isConstant()) {
throw new AnalysisException(fnName.getFunction()
+ " function except for the first argument, other parameter must be a constant.");
}
}
}
}
if (fnName.getFunction().equalsIgnoreCase("lag")
|| fnName.getFunction().equalsIgnoreCase("lead")) {
if (!isAnalyticFnCall) {
throw new AnalysisException(fnName.getFunction() + " only used in analytic function");
} else {
if (children.size() > 2) {
if (!getChild(1).isConstant() || !getChild(2).isConstant()) {
throw new AnalysisException(
"The default parameter (parameter 2 or parameter 3) of LEAD/LAG must be a constant: "
+ this.toSql());
}
uncheckedCastChild(Type.BIGINT, 1);
if (!getChild(2).type.matchesType(getChild(0).type) && !getChild(2).type.matchesType(Type.NULL)) {
uncheckedCastChild(getChild(0).type, 2);
}
}
return;
}
}
if (fnName.getFunction().equalsIgnoreCase("dense_rank")
|| fnName.getFunction().equalsIgnoreCase("rank")
|| fnName.getFunction().equalsIgnoreCase("row_number")
|| fnName.getFunction().equalsIgnoreCase("first_value")
|| fnName.getFunction().equalsIgnoreCase("last_value")
|| fnName.getFunction().equalsIgnoreCase("first_value_rewrite")
|| fnName.getFunction().equalsIgnoreCase("ntile")) {
if (!isAnalyticFnCall) {
throw new AnalysisException(fnName.getFunction() + " only used in analytic function");
}
}
// Function's arg can't be null for the following functions.
Expr arg = getChild(0);
if (arg == null) {
return;
}
// SUM and AVG cannot be applied to non-numeric types
if ((fnName.getFunction().equalsIgnoreCase("sum")
|| fnName.getFunction().equalsIgnoreCase("avg"))
&& ((!arg.type.isNumericType() && !arg.type.isNull() && !arg.type.isBoolean())
|| arg.type.isOnlyMetricType())) {
throw new AnalysisException(fnName.getFunction() + " requires a numeric parameter: " + this.toSql());
}
// DecimalV3 scale lower than DEFAULT_MIN_AVG_DECIMAL128_SCALE should do cast
if (fnName.getFunction().equalsIgnoreCase("avg") && arg.type.isDecimalV3()
&& arg.type.getDecimalDigits() < ScalarType.DEFAULT_MIN_AVG_DECIMAL128_SCALE) {
int precision = arg.type.getPrecision();
int scale = arg.type.getDecimalDigits();
precision = precision - scale + ScalarType.DEFAULT_MIN_AVG_DECIMAL128_SCALE;
scale = ScalarType.DEFAULT_MIN_AVG_DECIMAL128_SCALE;
if (SessionVariable.getEnableDecimal256()) {
if (precision > ScalarType.MAX_DECIMAL256_PRECISION) {
precision = ScalarType.MAX_DECIMAL256_PRECISION;
}
} else {
if (precision > ScalarType.MAX_DECIMAL128_PRECISION) {
precision = ScalarType.MAX_DECIMAL128_PRECISION;
}
}
Type t = ScalarType.createDecimalType(arg.type.getPrimitiveType(), precision, scale);
Expr e = getChild(0).castTo(t);
setChild(0, e);
}
if (fnName.getFunction().equalsIgnoreCase("sum_distinct")
&& ((!arg.type.isNumericType() && !arg.type.isNull()) || arg.type.isOnlyMetricType())) {
throw new AnalysisException(
"SUM_DISTINCT requires a numeric parameter: " + this.toSql());
}
if ((fnName.getFunction().equalsIgnoreCase("min")
|| fnName.getFunction().equalsIgnoreCase("max")
|| fnName.getFunction().equalsIgnoreCase("DISTINCT_PC")
|| fnName.getFunction().equalsIgnoreCase("DISTINCT_PCSA")
|| fnName.getFunction().equalsIgnoreCase("NDV"))
&& arg.type.isOnlyMetricType()) {
throw new AnalysisException(Type.OnlyMetricTypeErrorMsg);
}
if ((fnName.getFunction().equalsIgnoreCase(FunctionSet.BITMAP_UNION_INT) && !arg.type.isInteger32Type())) {
throw new AnalysisException("BITMAP_UNION_INT params only support TINYINT or SMALLINT or INT");
}
if (fnName.getFunction().equalsIgnoreCase(FunctionSet.INTERSECT_COUNT) || fnName.getFunction()
.equalsIgnoreCase(FunctionSet.ORTHOGONAL_BITMAP_INTERSECT) || fnName.getFunction()
.equalsIgnoreCase(FunctionSet.ORTHOGONAL_BITMAP_INTERSECT_COUNT) || fnName.getFunction()
.equalsIgnoreCase(FunctionSet.ORTHOGONAL_BITMAP_EXPR_CALCULATE_COUNT) || fnName.getFunction()
.equalsIgnoreCase(FunctionSet.ORTHOGONAL_BITMAP_EXPR_CALCULATE)) {
if (children.size() <= 2) {
throw new AnalysisException(fnName + "(bitmap_column, column_to_filter, filter_values) "
+ "function requires at least three parameters");
}
Type inputType = getChild(0).getType();
if (!inputType.isBitmapType()) {
throw new AnalysisException(
fnName + "function first argument should be of BITMAP type, but was " + inputType);
}
for (int i = 2; i < children.size(); i++) {
if (!getChild(i).isConstant()) {
throw new AnalysisException(fnName + " function filter_values arg must be constant");
}
}
return;
}
if (fnName.getFunction().equalsIgnoreCase(FunctionSet.BITMAP_COUNT)
|| fnName.getFunction().equalsIgnoreCase(FunctionSet.BITMAP_UNION)
|| fnName.getFunction().equalsIgnoreCase(FunctionSet.BITMAP_UNION_COUNT)
|| fnName.getFunction().equalsIgnoreCase(FunctionSet.BITMAP_INTERSECT)) {
if (children.size() != 1) {
throw new AnalysisException(fnName + " function could only have one child");
}
Type inputType = getChild(0).getType();
if (!inputType.isBitmapType()) {
throw new AnalysisException(fnName
+ " function's argument should be of BITMAP type, but was " + inputType);
}
return;
}
if (fnName.getFunction().equalsIgnoreCase(FunctionSet.QUANTILE_UNION)) {
if (children.size() != 1) {
throw new AnalysisException(fnName + " function could only have one child");
}
Type inputType = getChild(0).getType();
if (!inputType.isQuantileStateType()) {
throw new AnalysisException(fnName
+ " function's argument should be of QUANTILE_STATE type, but was" + inputType);
}
}
if (fnName.getFunction().equalsIgnoreCase(FunctionSet.TO_QUANTILE_STATE)) {
if (children.size() != 2) {
throw new AnalysisException(fnName + "function must have two children");
}
if (!getChild(1).isConstant()) {
throw new AnalysisException(fnName + "function's second argument should be constant");
}
}
if ((fnName.getFunction().equalsIgnoreCase("HLL_UNION_AGG")
|| fnName.getFunction().equalsIgnoreCase("HLL_CARDINALITY")
|| fnName.getFunction().equalsIgnoreCase("HLL_RAW_AGG")
|| fnName.getFunction().equalsIgnoreCase("HLL_UNION"))
&& !arg.type.isHllType()) {
throw new AnalysisException(
"HLL_UNION, HLL_UNION_AGG, HLL_RAW_AGG and HLL_CARDINALITY's params must be hll column");
}
if (fnName.getFunction().equalsIgnoreCase("min")
|| fnName.getFunction().equalsIgnoreCase("max")) {
fnParams.setIsDistinct(false); // DISTINCT is meaningless here
} else if (fnName.getFunction().equalsIgnoreCase("DISTINCT_PC")
|| fnName.getFunction().equalsIgnoreCase("DISTINCT_PCSA")
|| fnName.getFunction().equalsIgnoreCase("NDV")
|| fnName.getFunction().equalsIgnoreCase("HLL_UNION_AGG")) {
fnParams.setIsDistinct(false);
}
if (fnName.getFunction().equalsIgnoreCase("percentile")) {
if (children.size() != 2) {
throw new AnalysisException("percentile(expr, DOUBLE) requires two parameters");
}
if (!getChild(1).isConstant()) {
throw new AnalysisException("percentile requires second parameter must be a constant : "
+ this.toSql());
}
}
if (fnName.getFunction().equalsIgnoreCase("percentile_approx")) {
if (children.size() != 2 && children.size() != 3) {
throw new AnalysisException("percentile_approx(expr, DOUBLE [, B]) requires two or three parameters");
}
if (!getChild(1).isConstant()) {
throw new AnalysisException("percentile_approx requires second parameter must be a constant : "
+ this.toSql());
}
if (children.size() == 3) {
if (!getChild(2).isConstant()) {
throw new AnalysisException("percentile_approx requires the third parameter must be a constant : "
+ this.toSql());
}
}
}
if (fnName.getFunction().equalsIgnoreCase("topn")) {
if (children.size() != 2 && children.size() != 3) {
throw new AnalysisException("topn(expr, INT [, B]) requires two or three parameters");
}
if (!getChild(1).isConstant() || !getChild(1).getType().isIntegerType()) {
throw new AnalysisException("topn requires second parameter must be a constant Integer Type: "
+ this.toSql());
}
if (!getChild(1).getType().equals(ScalarType.INT)) {
Expr e = getChild(1).castTo(ScalarType.INT);
setChild(1, e);
}
if (children.size() == 3) {
if (!getChild(2).isConstant() || !getChild(2).getType().isIntegerType()) {
throw new AnalysisException("topn requires the third parameter must be a constant Integer Type: "
+ this.toSql());
}
if (!getChild(2).getType().equals(ScalarType.INT)) {
Expr e = getChild(2).castTo(ScalarType.INT);
setChild(2, e);
}
}
}
if ((fnName.getFunction().equalsIgnoreCase("aes_decrypt")
|| fnName.getFunction().equalsIgnoreCase("aes_encrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_decrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_encrypt"))
&& (children.size() == 2 || children.size() == 3)) {
Set<String> aesModes = new HashSet<>(Arrays.asList(
"AES_128_ECB",
"AES_192_ECB",
"AES_256_ECB",
"AES_128_CBC",
"AES_192_CBC",
"AES_256_CBC",
"AES_128_CFB",
"AES_192_CFB",
"AES_256_CFB",
"AES_128_CFB1",
"AES_192_CFB1",
"AES_256_CFB1",
"AES_128_CFB8",
"AES_192_CFB8",
"AES_256_CFB8",
"AES_128_CFB128",
"AES_192_CFB128",
"AES_256_CFB128",
"AES_128_CTR",
"AES_192_CTR",
"AES_256_CTR",
"AES_128_OFB",
"AES_192_OFB",
"AES_256_OFB"
));
Set<String> sm4Modes = new HashSet<>(Arrays.asList(
"SM4_128_ECB",
"SM4_128_CBC",
"SM4_128_CFB128",
"SM4_128_OFB",
"SM4_128_CTR"));
String blockEncryptionMode = "";
if (ConnectContext.get() != null) {
blockEncryptionMode = ConnectContext.get().getSessionVariable().getBlockEncryptionMode();
if (fnName.getFunction().equalsIgnoreCase("aes_decrypt")
|| fnName.getFunction().equalsIgnoreCase("aes_encrypt")) {
if (StringUtils.isAllBlank(blockEncryptionMode)) {
blockEncryptionMode = "AES_128_ECB";
}
if (!aesModes.contains(blockEncryptionMode.toUpperCase())) {
throw new AnalysisException("session variable block_encryption_mode is invalid with aes");
}
}
if (fnName.getFunction().equalsIgnoreCase("sm4_decrypt")
|| fnName.getFunction().equalsIgnoreCase("sm4_encrypt")) {
if (StringUtils.isAllBlank(blockEncryptionMode)) {
blockEncryptionMode = "SM4_128_ECB";
}
if (!sm4Modes.contains(blockEncryptionMode.toUpperCase())) {
throw new AnalysisException(
"session variable block_encryption_mode is invalid with sm4");
}
}
} else {
throw new AnalysisException("cannot get session variable `block_encryption_mode`, "
+ "please explicitly specify by using 4-args function");
}
children.add(new StringLiteral(blockEncryptionMode));
}
}
private void analyzeArrayFunction(Analyzer analyzer) throws AnalysisException {
boolean enableDecimal256 = SessionVariable.getEnableDecimal256();
if (fnName.getFunction().equalsIgnoreCase("array_distinct")
|| fnName.getFunction().equalsIgnoreCase("array_max")
|| fnName.getFunction().equalsIgnoreCase("array_min")
|| fnName.getFunction().equalsIgnoreCase("array_sum")
|| fnName.getFunction().equalsIgnoreCase("array_avg")
|| fnName.getFunction().equalsIgnoreCase("array_product")
|| fnName.getFunction().equalsIgnoreCase("array_union")
|| fnName.getFunction().equalsIgnoreCase("array_except")
|| fnName.getFunction().equalsIgnoreCase("array_cum_sum")
|| fnName.getFunction().equalsIgnoreCase("array_intersect")
|| fnName.getFunction().equalsIgnoreCase("arrays_overlap")
|| fnName.getFunction().equalsIgnoreCase("array_concat")
|| fnName.getFunction().equalsIgnoreCase("array")) {
Type[] childTypes = collectChildReturnTypes();
Type compatibleType = childTypes[0];
for (int i = 1; i < childTypes.length; ++i) {
compatibleType = Type.getAssignmentCompatibleType(compatibleType, childTypes[i], true,
enableDecimal256);
if (compatibleType == Type.INVALID) {
throw new AnalysisException(getFunctionNotFoundError(collectChildReturnTypes()));
}
}
// Make sure BE doesn't see any TYPE_NULL exprs
if (compatibleType.isNull()) {
compatibleType = Type.BOOLEAN;
}
for (int i = 0; i < childTypes.length; i++) {
uncheckedCastChild(compatibleType, i);
}
} else if (fnName.getFunction().equalsIgnoreCase("array_exists")) {
Type[] newArgTypes = new Type[1];
if (!(getChild(0) instanceof CastExpr)) {
Expr castExpr = getChild(0).castTo(ArrayType.create(Type.BOOLEAN, true));
this.setChild(0, castExpr);
newArgTypes[0] = castExpr.getType();
}
fn = getBuiltinFunction(fnName.getFunction(), newArgTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
if (fn == null) {
LOG.warn("fn {} not exists", this.toSqlImpl());
throw new AnalysisException(getFunctionNotFoundError(collectChildReturnTypes()));
}
fn.setReturnType(getChild(0).getType());
} else if (fnName.getFunction().equalsIgnoreCase("array_position")
|| fnName.getFunction().equalsIgnoreCase("array_contains")
|| fnName.getFunction().equalsIgnoreCase("countequal")) {
// make nested type with function param can be Compatible otherwise be will not deal with type
Type[] childTypes = collectChildReturnTypes();
if (childTypes[0].isNull()) {
childTypes[0] = new ArrayType(Type.NULL);
}
Type compatibleType = ((ArrayType) childTypes[0]).getItemType();
for (int i = 1; i < childTypes.length; ++i) {
compatibleType = Type.getAssignmentCompatibleType(compatibleType, childTypes[i], true,
enableDecimal256);
if (compatibleType == Type.INVALID) {
throw new AnalysisException(getFunctionNotFoundError(collectChildReturnTypes()));
}
uncheckedCastChild(compatibleType, i);
}
}
}
// Provide better error message for some aggregate builtins. These can be
// a bit more user friendly than a generic function not found.
// TODO: should we bother to do this? We could also improve the general
// error messages. For example, listing the alternatives.
protected String getFunctionNotFoundError(Type[] argTypes) {
// Some custom error message for builtins
if (fnParams.isStar()) {
return "'*' can only be used in conjunction with COUNT";
}
if (fnName.getFunction().equalsIgnoreCase(FunctionSet.COUNT)) {
if (!fnParams.isDistinct() && argTypes.length > 1) {
return "COUNT must have DISTINCT for multiple arguments: " + toSql();
}
}
if (fnName.getFunction().equalsIgnoreCase("sum")) {
return "SUM requires a numeric parameter: " + toSql();
}
if (fnName.getFunction().equalsIgnoreCase("avg")) {
return "AVG requires a numeric or timestamp parameter: " + toSql();
}
String[] argTypesSql = new String[argTypes.length];
for (int i = 0; i < argTypes.length; ++i) {
argTypesSql[i] = argTypes[i].toSql();
}
return String.format(
"No matching function with signature: %s(%s).",
fnName, fnParams.isStar() ? "*" : Joiner.on(", ").join(argTypesSql));
}
/**
* 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);
}
}
}
@Override
public void analyzeImpl(Analyzer analyzer) throws AnalysisException {
if (isMergeAggFn) {
// This is the function call expr after splitting up to a merge aggregation.
// The function has already been analyzed so just do the minimal sanity
// check here.
Preconditions.checkNotNull(fn);
return;
}
if (fnName.getFunction().equals(FunctionSet.COUNT) && fnParams.isDistinct()) {
// Treat COUNT(DISTINCT ...) special because of how we do the equal.
// There is no version of COUNT() that takes more than 1 argument but after
// the equal, we only need count(*).
// TODO: fix how we equal count distinct.
fn = getBuiltinFunction(fnName.getFunction(), new Type[0], Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
type = fn.getReturnType();
if (children.get(0).type.isComplexType()) {
throw new AnalysisException("The pattern params of " + fnName + " function can not support "
+ children.get(0).type);
}
// Make sure BE doesn't see any TYPE_NULL exprs
for (int i = 0; i < children.size(); ++i) {
if (getChild(i).getType().isNull()) {
uncheckedCastChild(Type.BOOLEAN, i);
}
}
return;
}
Type[] argTypes = new Type[this.children.size()];
for (int i = 0; i < this.children.size(); ++i) {
this.children.get(i).analyze(analyzer);
argTypes[i] = this.children.get(i).getType();
}
analyzeBuiltinAggFunction(analyzer);
analyzeArrayFunction(analyzer);
boolean enableDecimal256 = SessionVariable.getEnableDecimal256();
if (fnName.getFunction().equalsIgnoreCase("sum")) {
if (this.children.isEmpty()) {
throw new AnalysisException("The " + fnName + " function must has one input param");
}
// Prevent the cast type in vector exec engine
Type type = getChild(0).type;
fn = getBuiltinFunction(fnName.getFunction(), new Type[] { type },
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase("count_distinct")) {
Type compatibleType = this.children.get(0).getType();
for (int i = 1; i < this.children.size(); ++i) {
Type type = this.children.get(i).getType();
compatibleType = Type.getAssignmentCompatibleType(compatibleType, type, true, enableDecimal256);
if (compatibleType.isInvalid()) {
compatibleType = Type.VARCHAR;
break;
}
}
fn = getBuiltinFunction(fnName.getFunction(), new Type[] { compatibleType },
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase(FunctionSet.WINDOW_FUNNEL)) {
if (fnParams.exprs() == null || fnParams.exprs().size() < 4) {
throw new AnalysisException("The " + fnName + " function must have at least four params");
}
if (!children.get(0).type.isIntegerType()) {
throw new AnalysisException("The window params of " + fnName + " function must be integer");
}
if (!children.get(1).type.isStringType()) {
throw new AnalysisException("The mode params of " + fnName + " function must be string");
}
if (!children.get(2).type.isDateType()) {
throw new AnalysisException("The 3rd param of " + fnName + " function must be DATE or DATETIME");
}
Type[] childTypes = new Type[children.size()];
for (int i = 0; i < 3; i++) {
childTypes[i] = children.get(i).type;
}
for (int i = 3; i < children.size(); i++) {
if (children.get(i).type != Type.BOOLEAN) {
throw new AnalysisException("The 4th and subsequent params of "
+ fnName + " function must be boolean");
}
childTypes[i] = children.get(i).type;
}
fn = getBuiltinFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
if (fn != null && childTypes[2].isDate()) {
// cast date to datetime
uncheckedCastChild(ScalarType.DATETIME, 2);
} else if (fn != null && childTypes[2].isDateV2()) {
// cast date to datetime
uncheckedCastChild(ScalarType.DATETIMEV2, 2);
}
} else if (fnName.getFunction().equalsIgnoreCase(FunctionSet.RETENTION)) {
if (this.children.isEmpty()) {
throw new AnalysisException("The " + fnName + " function must have at least one param");
}
Type[] childTypes = new Type[children.size()];
for (int i = 0; i < children.size(); i++) {
if (children.get(i).type != Type.BOOLEAN) {
throw new AnalysisException("All params of "
+ fnName + " function must be boolean");
}
childTypes[i] = children.get(i).type;
}
fn = getBuiltinFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase(FunctionSet.SEQUENCE_MATCH)
|| fnName.getFunction().equalsIgnoreCase(FunctionSet.SEQUENCE_COUNT)) {
if (fnParams.exprs() == null || fnParams.exprs().size() < 4) {
throw new AnalysisException("The " + fnName + " function must have at least four params");
}
if (!children.get(0).type.isStringType()) {
throw new AnalysisException("The pattern params of " + fnName + " function must be string");
}
if (!children.get(1).type.isDateType()) {
throw new AnalysisException("The timestamp params of " + fnName + " function must be DATE or DATETIME");
}
String pattern = children.get(0).toSql();
int patternLength = pattern.length();
pattern = pattern.substring(1, patternLength - 1);
if (!parsePattern(pattern)) {
throw new AnalysisException("The format of pattern params is wrong");
}
Type[] childTypes = new Type[children.size()];
for (int i = 0; i < 2; i++) {
childTypes[i] = children.get(i).type;
}
for (int i = 2; i < children.size(); i++) {
if (children.get(i).type != Type.BOOLEAN) {
throw new AnalysisException("The 3th and subsequent params of "
+ fnName + " function must be boolean");
}
childTypes[i] = children.get(i).type;
}
fn = getBuiltinFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase("if")) {
Type[] childTypes = collectChildReturnTypes();
Type assignmentCompatibleType = ScalarType.getAssignmentCompatibleType(childTypes[1], childTypes[2], true,
enableDecimal256);
if (assignmentCompatibleType.isDecimalV3()) {
if (assignmentCompatibleType.isDecimalV3() && !childTypes[1].equals(assignmentCompatibleType)) {
uncheckedCastChild(assignmentCompatibleType, 1);
}
if (assignmentCompatibleType.isDecimalV3() && !childTypes[2].equals(assignmentCompatibleType)) {
uncheckedCastChild(assignmentCompatibleType, 2);
}
}
childTypes[0] = Type.BOOLEAN;
childTypes[1] = assignmentCompatibleType;
childTypes[2] = assignmentCompatibleType;
if (childTypes[1].isDecimalV3() && childTypes[2].isDecimalV3()) {
argTypes[1] = assignmentCompatibleType;
argTypes[2] = assignmentCompatibleType;
}
fn = getBuiltinFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
if (assignmentCompatibleType.isDatetimeV2()) {
fn.setReturnType(assignmentCompatibleType);
}
} else if (fnName.getFunction().equalsIgnoreCase("ifnull")
|| fnName.getFunction().equalsIgnoreCase("nvl")) {
Preconditions.checkArgument(children != null && children.size() == 2,
"The " + fnName + " function must have two params");
Type[] childTypes = collectChildReturnTypes();
Type assignmentCompatibleType = ScalarType.getAssignmentCompatibleType(childTypes[0], childTypes[1], true,
enableDecimal256);
if (assignmentCompatibleType != Type.INVALID) {
if (assignmentCompatibleType.isDecimalV3()) {
if (assignmentCompatibleType.isDecimalV3() && !childTypes[0].equals(assignmentCompatibleType)) {
uncheckedCastChild(assignmentCompatibleType, 0);
}
if (assignmentCompatibleType.isDecimalV3() && !childTypes[1].equals(assignmentCompatibleType)) {
uncheckedCastChild(assignmentCompatibleType, 1);
}
}
childTypes[0] = assignmentCompatibleType;
childTypes[1] = assignmentCompatibleType;
if (childTypes[1].isDecimalV3() && childTypes[0].isDecimalV3()) {
argTypes[1] = assignmentCompatibleType;
argTypes[0] = assignmentCompatibleType;
}
}
fn = getBuiltinFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if ((fnName.getFunction().equalsIgnoreCase("coalesce")
|| fnName.getFunction().equalsIgnoreCase("least")
|| fnName.getFunction().equalsIgnoreCase("greatest")) && children.size() > 1) {
Type[] childTypes = collectChildReturnTypes();
Type assignmentCompatibleType = childTypes[0];
for (int i = 1; i < childTypes.length; i++) {
assignmentCompatibleType = ScalarType
.getAssignmentCompatibleType(assignmentCompatibleType, childTypes[i], true, enableDecimal256);
}
if (assignmentCompatibleType.isDecimalV3()) {
for (int i = 0; i < childTypes.length; i++) {
if (assignmentCompatibleType.isDecimalV3()
&& !childTypes[i].equals(assignmentCompatibleType)) {
uncheckedCastChild(assignmentCompatibleType, i);
argTypes[i] = assignmentCompatibleType;
}
}
} else if (assignmentCompatibleType.isDateV2OrDateTimeV2()) {
for (int i = 0; i < childTypes.length; i++) {
if (assignmentCompatibleType.isDateV2OrDateTimeV2()
&& !childTypes[i].equals(assignmentCompatibleType)) {
uncheckedCastChild(assignmentCompatibleType, i);
argTypes[i] = assignmentCompatibleType;
}
}
}
fn = getBuiltinFunction(fnName.getFunction(), argTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase("array_apply")
&& ((ArrayType) children.get(0).getType()).getItemType().isDecimalV3()) {
uncheckedCastChild(((ArrayType) children.get(0).getType()).getItemType(), 2);
fn = getBuiltinFunction(fnName.getFunction(), collectChildReturnTypes(),
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (AggregateFunction.SUPPORT_ORDER_BY_AGGREGATE_FUNCTION_NAME_SET.contains(
fnName.getFunction().toLowerCase())) {
// order by elements add as child like windows function. so if we get the
// param of arg, we need remove the order by elements
Type[] childTypes = collectChildReturnTypes();
Type[] newChildTypes = new Type[children.size() - orderByElements.size()];
System.arraycopy(childTypes, 0, newChildTypes, 0, newChildTypes.length);
fn = getBuiltinFunction(fnName.getFunction(), newChildTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (STDDEV_FUNCTION_SET.contains(fnName.getFunction().toLowerCase())
&& collectChildReturnTypes()[0].isDecimalV3()) {
Type[] childrenTypes = collectChildReturnTypes();
Type[] args = new Type[childrenTypes.length];
args[0] = Type.DOUBLE;
System.arraycopy(childrenTypes, 1, args, 1, childrenTypes.length - 1);
fn = getBuiltinFunction(fnName.getFunction(), args, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase("bitand")
|| fnName.getFunction().equalsIgnoreCase("bitor")
|| fnName.getFunction().equalsIgnoreCase("bitxor")) {
Type[] childTypes = collectChildReturnTypes();
if (Arrays.stream(childTypes).anyMatch(child -> child.isDecimalV2()
|| child.isDecimalV3() || child.isFloatingPointType())) {
uncheckedCastChild(Type.BIGINT, 0);
uncheckedCastChild(Type.BIGINT, 1);
argTypes[0] = Type.BIGINT;
argTypes[1] = Type.BIGINT;
}
fn = getBuiltinFunction(fnName.getFunction(), argTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase("bitnot")) {
if (children.get(0).type.isDecimalV2() || children.get(0).type.isDecimalV3()
|| children.get(0).type.isFloatingPointType()) {
uncheckedCastChild(Type.BIGINT, 0);
argTypes[0] = Type.BIGINT;
}
fn = getBuiltinFunction(fnName.getFunction(), argTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else if (fnName.getFunction().equalsIgnoreCase("date_add")
|| fnName.getFunction().equalsIgnoreCase("days_add")
|| fnName.getFunction().equalsIgnoreCase("adddate")
|| fnName.getFunction().equalsIgnoreCase("date_sub")
|| fnName.getFunction().equalsIgnoreCase("days_sub")
|| fnName.getFunction().equalsIgnoreCase("subdate")) {
Type[] childTypes = collectChildReturnTypes();
argTypes[0] = childTypes[0];
argTypes[1] = childTypes[1];
if (childTypes[1] == Type.TINYINT || childTypes[1] == Type.SMALLINT) {
// be only support second param as int type
uncheckedCastChild(Type.INT, 1);
argTypes[1] = Type.INT;
}
fn = getBuiltinFunction(fnName.getFunction(), argTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else {
// now first find table function in table function sets
if (isTableFnCall) {
Type[] childTypes = collectChildReturnTypes();
// when we call explode<Array<Decimal>> with nested decimal has specific precision and scale,
// collectChildReturnTypes will return specific precision and scale decimal type witch may not match
// builtln func we defined in fe code, because we make array_support_type is actual origin type.here we
// temp write this if to get matched explode function and then set actually decimal type from sql to
// func return type. if we switch nereid would hasn't this problems.
if (fnName.getFunction().equalsIgnoreCase("explode") && childTypes[0].isArrayType()) {
// get origin type to match builtln func
Type[] matchFuncChildTypes = getActualArgTypes(childTypes);
fn = getTableFunction(fnName.getFunction(), matchFuncChildTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
if (fn == null) {
throw new AnalysisException(getFunctionNotFoundError(argTypes) + " in table function");
}
// set param child types
fn.setReturnType(((ArrayType) childTypes[0]).getItemType());
} else {
fn = getTableFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
}
// find user defined functions
if (fn == null) {
fn = findUdf(fnName, analyzer);
if (fn != null) {
FunctionUtil.checkEnableJavaUdf();
if (!fn.isUDTFunction()) {
throw new AnalysisException(getFunctionNotFoundError(argTypes) + " in table function");
}
}
}
if (fn == null) {
throw new AnalysisException(getFunctionNotFoundError(argTypes));
}
} else {
if (ROUND_FUNCTION_SET.contains(fnName.getFunction()) && children.size() == 2
&& children.get(0).getType().isDecimalV3() && children.get(1) instanceof IntLiteral) {
children.get(1).setType(Type.INT);
}
// now first find function in built-in functions
if (Strings.isNullOrEmpty(fnName.getDb())) {
Type[] childTypes = collectChildReturnTypes();
// when we call count<Array<T>> with nested type is not null type which is defined in FunctionSet
// so here aim to make function signature to match builtln func we defined in fe code
if (fnName.getFunction().equalsIgnoreCase("count") && childTypes.length > 0
&& childTypes[0].isComplexType()) {
// get origin type to match builtln func
Type[] matchFuncChildTypes = new Type[1];
if (childTypes[0].isArrayType()) {
matchFuncChildTypes[0] = Type.ARRAY;
} else if (childTypes[0].isMapType()) {
matchFuncChildTypes[0] = Type.MAP;
} else if (childTypes[0].isStructType()) {
matchFuncChildTypes[0] = Type.GENERIC_STRUCT;
}
fn = getBuiltinFunction(fnName.getFunction(), matchFuncChildTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
} else {
fn = getBuiltinFunction(fnName.getFunction(), childTypes,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
}
}
// find user defined functions
if (fn == null) {
fn = findUdf(fnName, analyzer);
if (analyzer.isReAnalyze() && fn instanceof AliasFunction) {
throw new AnalysisException("a UDF in the original function of a alias function");
}
if (fn != null) {
FunctionUtil.checkEnableJavaUdf();
}
}
}
}
if (fn == null) {
LOG.warn("fn {} not exists", this.toSqlImpl());
throw new AnalysisException(getFunctionNotFoundError(collectChildReturnTypes()));
}
if (fnName.getFunction().equalsIgnoreCase("collect_list")
|| fnName.getFunction().equalsIgnoreCase("collect_set")
|| fnName.getFunction().equalsIgnoreCase("array_agg")) {
fn.setReturnType(new ArrayType(getChild(0).type));
}
if (fnName.getFunction().equalsIgnoreCase("map_agg")) {
fn.setReturnType(new MapType(getChild(0).type, getChild(1).type));
}
if (fnName.getFunction().equalsIgnoreCase("group_uniq_array")
|| fnName.getFunction().equalsIgnoreCase("group_array")) {
fn.setReturnType(new ArrayType(getChild(0).type));
}
if (fnName.getFunction().equalsIgnoreCase("from_unixtime")
|| fnName.getFunction().equalsIgnoreCase("date_format")
|| fnName.getFunction().equalsIgnoreCase("unix_timestamp")) {
// if has only one child, it has default time format: yyyy-MM-dd HH:mm:ss.SSSSSS
if (children.size() > 1) {
final StringLiteral fmtLiteral = (StringLiteral) children.get(1);
if (fmtLiteral.getStringValue().equals("yyyyMMdd")) {
children.set(1, new StringLiteral("%Y%m%d"));
} else if (fmtLiteral.getStringValue().equals("yyyy-MM-dd")) {
children.set(1, new StringLiteral("%Y-%m-%d"));
} else if (fmtLiteral.getStringValue().equals("yyyy-MM-dd HH:mm:ss")) {
children.set(1, new StringLiteral("%Y-%m-%d %H:%i:%s"));
}
}
if (fnName.getFunction().equalsIgnoreCase("unix_timestamp") && children.size() == 1) {
if (getChild(0).type.isDatetimeV2()) {
ScalarType type = (ScalarType) getChild(0).type;
Preconditions.checkArgument(type.getScalarScale() <= 6,
"DatetimeV2's scale shouldn't exceed 6 but meet " + type.getScalarScale());
fn.setReturnType(
ScalarType.createDecimalType(PrimitiveType.DECIMAL64, 10 + type.getScalarScale(),
type.getScalarScale()));
} else if (getChild(0).type.isStringType()) {
// use DATETIME to make scale adaptive
ScalarType type = ((ScalarType) (getChild(0).uncheckedCastTo(ScalarType.DATETIME).type));
if (type.isDatetimeV2()) {
int scale = type.getScalarScale();
fn.setReturnType(
ScalarType.createDecimalType(PrimitiveType.DECIMAL64, 10 + scale, scale));
}
}
}
}
if (fnName.getFunction().equalsIgnoreCase("convert_to")) {
if (children.size() < 2 || !getChild(1).isConstant()) {
throw new AnalysisException(
fnName.getFunction() + " needs two params, and the second is must be a constant: " + this
.toSql());
}
}
if (fnName.getFunction().equalsIgnoreCase("date_trunc")) {
if ((children.size() != 2) || (getChild(1).isConstant() == false)
|| !(getChild(1) instanceof StringLiteral)) {
throw new AnalysisException(
fnName.getFunction() + " needs two params, and the second is must be a string constant: "
+ this.toSql());
}
final String constParam = ((StringLiteral) getChild(1)).getValue().toLowerCase();
if (!Lists.newArrayList("year", "quarter", "month", "week", "day", "hour", "minute", "second")
.contains(constParam)) {
throw new AnalysisException("date_trunc function second param only support argument is "
+ "year|quarter|month|week|day|hour|minute|second");
}
}
if (fnName.getFunction().equalsIgnoreCase("array_range")
|| fnName.getFunction().equalsIgnoreCase("sequence")) {
if (getChild(0) instanceof DateLiteral && !(getChild(2) instanceof StringLiteral)) {
throw new AnalysisException("To generate datetime array, please use interval literal like: "
+ "interval 1 day.");
}
}
if (fnName.getFunction().equalsIgnoreCase("char")) {
if (!getChild(0).isConstant()) {
throw new AnalysisException(
fnName.getFunction() + " charset name must be a constant: " + this
.toSql());
}
LiteralExpr literal = (LiteralExpr) getChild(0);
if (!literal.getStringValue().equalsIgnoreCase("utf8")) {
throw new AnalysisException(
fnName.getFunction() + " function currently only support charset name 'utf8': " + this
.toSql());
}
}
if (fn.getFunctionName().getFunction().equalsIgnoreCase("timediff")) {
fn.getReturnType().getPrimitiveType().setTimeType();
ScalarType left = (ScalarType) argTypes[0];
ScalarType right = (ScalarType) argTypes[1];
if (left.isDatetimeV2() || right.isDatetimeV2() || left.isDateV2() || right.isDateV2()) {
Type ret = ScalarType.createTimeV2Type(Math.max(left.getScalarScale(), right.getScalarScale()));
fn.setReturnType(ret);
fn.getReturnType().getPrimitiveType().setTimeType();
}
}
if (fn.getFunctionName().getFunction().equalsIgnoreCase("from_microsecond")) {
Type ret = ScalarType.createDatetimeV2Type(6);
fn.setReturnType(ret);
}
if (fn.getFunctionName().getFunction().equalsIgnoreCase("from_millisecond")) {
Type ret = ScalarType.createDatetimeV2Type(3);
fn.setReturnType(ret);
}
if (fn.getFunctionName().getFunction().equalsIgnoreCase("from_second")) {
Type ret = ScalarType.createDatetimeV2Type(0);
fn.setReturnType(ret);
}
if (fnName.getFunction().equalsIgnoreCase("map")) {
if ((children.size() & 1) == 1) {
throw new AnalysisException("map can't be odd parameters, need even parameters: "
+ this.toSql());
}
}
if (fnName.getFunction().equalsIgnoreCase("named_struct")) {
if ((children.size() & 1) == 1) {
throw new AnalysisException("named_struct can't be odd parameters, need even parameters: "
+ this.toSql());
}
for (int i = 0; i < children.size(); i++) {
if ((i & 1) == 0) {
if (!(getChild(i) instanceof StringLiteral)) {
throw new AnalysisException(
"named_struct only allows constant string parameter in odd position: " + this.toSql());
}
}
}
}
if (fn.getFunctionName().getFunction().equalsIgnoreCase("struct_element")) {
if (children.size() < 2) {
throw new AnalysisException(fnName.getFunction() + " needs two parameters: " + this.toSql());
}
if (getChild(0).type instanceof StructType) {
StructType s = ((StructType) children.get(0).type);
if (getChild(1) instanceof StringLiteral) {
String fieldName = children.get(1).getStringValue();
if (s.getField(fieldName) == null) {
throw new AnalysisException(
"the specified field name " + fieldName + " was not found: " + this.toSql());
}
} else if (getChild(1) instanceof IntLiteral) {
int pos = (int) ((IntLiteral) children.get(1)).getValue();
if (pos < 1 || pos > s.getFields().size()) { // the index start from 1
throw new AnalysisException(
"the specified field index out of bound: " + this.toSql());
}
} else {
throw new AnalysisException(
"struct_element only allows constant int or string second parameter: " + this.toSql());
}
}
}
if (fn.getFunctionName().getFunction().equalsIgnoreCase("sha2")) {
if ((children.size() != 2) || (getChild(1).isConstant() == false)
|| !(getChild(1) instanceof IntLiteral)) {
throw new AnalysisException(
fnName.getFunction() + " needs two params, and the second is must be a integer constant: "
+ this.toSql());
}
final Integer constParam = (int) ((IntLiteral) getChild(1)).getValue();
if (!Lists.newArrayList(224, 256, 384, 512).contains(constParam)) {
throw new AnalysisException("sha2 functions only support digest length of 224/256/384/512");
}
}
if (isAggregateFunction()) {
final String functionName = fnName.getFunction();
// subexprs must not contain aggregates
if (Expr.containsAggregate(children)) {
throw new AnalysisException(
"aggregate function cannot contain aggregate parameters: " + this.toSql());
}
if (STDDEV_FUNCTION_SET.contains(functionName) && argTypes[0].isDateType()) {
throw new AnalysisException("Stddev/variance function do not support Date/Datetime type");
}
if (functionName.equalsIgnoreCase("multi_distinct_sum") && argTypes[0].isDateType()) {
throw new AnalysisException("Sum in multi distinct functions do not support Date/Datetime type");
}
} else {
if (fnParams.isStar()) {
throw new AnalysisException("Cannot pass '*' to scalar function.");
}
if (fnParams.isDistinct()) {
throw new AnalysisException("Cannot pass 'DISTINCT' to scalar function.");
}
}
Type[] args = fn.getArgs();
if (args.length > 0) {
// Implicitly cast all the children to match the function if necessary
for (int i = 0; i < argTypes.length - orderByElements.size(); ++i) {
// For varargs, we must compare with the last type in callArgs.argTypes.
int ix = Math.min(args.length - 1, i);
// map varargs special case map(key_type, value_type, ...)
if (i >= args.length && i >= 2 && args.length >= 2
&& fnName.getFunction().equalsIgnoreCase("map")) {
ix = i % 2 == 0 ? 0 : 1;
}
// array_zip varargs special case array_zip(array1, array2, ...)
// we only specialize array_zip with first array type, next type we same with custom type
if (i >= args.length && (fnName.getFunction().equalsIgnoreCase("array_zip"))) {
if (argTypes[i].isNull()) {
uncheckedCastChild(args[i - 1], i);
}
continue;
}
if (i == 0 && (fnName.getFunction().equalsIgnoreCase("char"))) {
continue;
}
if (fnName.getFunction().equalsIgnoreCase("count") && args[i].isComplexType()) {
continue;
}
if ((fnName.getFunction().equalsIgnoreCase("money_format") || fnName.getFunction()
.equalsIgnoreCase("histogram")
|| fnName.getFunction().equalsIgnoreCase("hist")
|| fnName.getFunction().equalsIgnoreCase("linear_histogram"))
&& children.get(0).getType().isDecimalV3() && args[ix].isDecimalV3()) {
continue;
} else if ((fnName.getFunction().equalsIgnoreCase("array_min") || fnName.getFunction()
.equalsIgnoreCase("array_max") || fnName.getFunction().equalsIgnoreCase("element_at"))
&& ((
children.get(0).getType().isDecimalV3() && ((ArrayType) args[ix]).getItemType()
.isDecimalV3())
|| (children.get(0).getType().isDatetimeV2()
&& ((ArrayType) args[ix]).getItemType().isDatetimeV2())
|| (children.get(0).getType().isDecimalV2()
&& ((ArrayType) args[ix]).getItemType().isDecimalV2()))) {
continue;
} else if ((fnName.getFunction().equalsIgnoreCase("array")
|| fnName.getFunction().equalsIgnoreCase("array_distinct")
|| fnName.getFunction().equalsIgnoreCase("array_remove")
|| fnName.getFunction().equalsIgnoreCase("array_sort")
|| fnName.getFunction().equalsIgnoreCase("array_reverse_sort")
|| fnName.getFunction().equalsIgnoreCase("array_overlap")
|| fnName.getFunction().equalsIgnoreCase("array_union")
|| fnName.getFunction().equalsIgnoreCase("array_intersect")
|| fnName.getFunction().equalsIgnoreCase("array_compact")
|| fnName.getFunction().equalsIgnoreCase("array_slice")
|| fnName.getFunction().equalsIgnoreCase("array_popback")
|| fnName.getFunction().equalsIgnoreCase("array_popfront")
|| fnName.getFunction().equalsIgnoreCase("array_pushfront")
|| fnName.getFunction().equalsIgnoreCase("array_pushback")
|| fnName.getFunction().equalsIgnoreCase("array_cum_sum")
|| fnName.getFunction().equalsIgnoreCase("reverse")
|| fnName.getFunction().equalsIgnoreCase("%element_slice%")
|| fnName.getFunction().equalsIgnoreCase("array_concat")
|| fnName.getFunction().equalsIgnoreCase("array_shuffle")
|| fnName.getFunction().equalsIgnoreCase("shuffle")
|| fnName.getFunction().equalsIgnoreCase("array_except")
|| fnName.getFunction().equalsIgnoreCase("array_apply")
|| fnName.getFunction().equalsIgnoreCase("array_position")
|| fnName.getFunction().equalsIgnoreCase("array_contains")
|| fnName.getFunction().equalsIgnoreCase("width_bucket"))
&& (args[ix].isDecimalV3() || (children.get(0).getType().isArrayType()
&& (((ArrayType) children.get(0).getType()).getItemType().isDecimalV3())
&& (args[ix].isArrayType())
&& ((ArrayType) args[ix]).getItemType().isDecimalV3()))) {
continue;
} else if (!argTypes[i].matchesType(args[ix])
&& ROUND_FUNCTION_SET.contains(fnName.getFunction())
&& ConnectContext.get() != null
&& ConnectContext.get().getSessionVariable().roundPreciseDecimalV2Value
&& argTypes[i].isDecimalV2()
&& args[ix].isDecimalV3()) {
uncheckedCastChild(ScalarType.createDecimalV3Type(ScalarType.MAX_DECIMALV2_PRECISION,
((ScalarType) argTypes[i]).getScalarScale()), i);
} else if (!argTypes[i].matchesType(args[ix])
&& !(argTypes[i].isDecimalV3OrContainsDecimalV3()
&& args[ix].isDecimalV3OrContainsDecimalV3())) {
// Do not do this cast if types are both decimalv3 with different precision/scale.
uncheckedCastChild(args[ix], i);
} else if (fnName.getFunction().equalsIgnoreCase("if")
&& argTypes[i].isArrayType() && ((ArrayType) argTypes[i]).getItemType().isNull()) {
uncheckedCastChild(args[ix], i);
}
}
}
/**
* The return type of str_to_date depends on whether the time part is included
* in the format.
* If included, it is datetime, otherwise it is date.
* If the format parameter is not constant, the return type will be datetime.
* The above judgment has been completed in the FE query planning stage,
* so here we directly set the value type to the return type set in the query
* plan.
*
* For example:
* A table with one column k1 varchar, and has 2 lines:
* "%Y-%m-%d"
* "%Y-%m-%d %H:%i:%s"
* Query:
* SELECT str_to_date("2020-09-01", k1) from tbl;
* Result will be:
* 2020-09-01 00:00:00
* 2020-09-01 00:00:00
*
* Query:
* SELECT str_to_date("2020-09-01", "%Y-%m-%d");
* Return type is DATE
*
* Query:
* SELECT str_to_date("2020-09-01", "%Y-%m-%d %H:%i:%s");
* Return type is DATETIME
*/
if (fn.getFunctionName().getFunction().equalsIgnoreCase("str_to_date")) {
Expr child1Result = getChild(1).getResultValue(false);
if (child1Result instanceof StringLiteral) {
if (DateLiteral.hasTimePart(child1Result.getStringValue())) {
this.type = Type.DATETIMEV2_WITH_MAX_SCALAR;
} else {
this.type = Type.DATEV2;
}
} else {
this.type = Type.DATETIMEV2_WITH_MAX_SCALAR;
}
} else if (TIME_FUNCTIONS_WITH_PRECISION.contains(fnName.getFunction().toLowerCase())
&& fn.getReturnType().isDatetimeV2()) {
if (children.size() == 1 && children.get(0) instanceof IntLiteral) {
this.type = ScalarType.createDatetimeV2Type((int) ((IntLiteral) children.get(0)).getLongValue());
} else if (children.size() == 1) {
this.type = ScalarType.createDatetimeV2Type(6);
}
} else {
this.type = fn.getReturnType();
}
if (this.type.isDecimalV2()) {
this.type = Type.MAX_DECIMALV2_TYPE;
fn.setReturnType(Type.MAX_DECIMALV2_TYPE);
}
if (this.type.isDecimalV3() || (this.type.isArrayType()
&& ((ArrayType) this.type).getItemType().isDecimalV3())
|| (this.type.isDatetimeV2()
&& !TIME_FUNCTIONS_WITH_PRECISION.contains(fnName.getFunction().toLowerCase()))) {
// TODO(gabriel): If type exceeds max precision of DECIMALV3, we should change
// it to a double function
this.type = PRECISION_INFER_RULE.getOrDefault(fnName.getFunction(), DEFAULT_PRECISION_INFER_RULE)
.apply(children, this.type);
}
// cast(xx as char(N)/varchar(N)) will be handled as substr(cast(xx as char, varchar), 1, N),
// but type is varchar(*), we change it to varchar(N);
if (fn.getFunctionName().getFunction().equalsIgnoreCase("substr")
&& children.size() == 3
&& children.get(1) instanceof IntLiteral
&& children.get(2) instanceof IntLiteral) {
long len = ((IntLiteral) children.get(2)).getValue();
if (type.isWildcardChar()) {
this.type = ScalarType.createCharType(((int) (len)));
} else if (type.isWildcardVarchar()) {
this.type = ScalarType.createVarchar(((int) (len)));
}
}
// rewrite return type if is nested type function
analyzeNestedFunction();
for (OrderByElement o : orderByElements) {
if (!o.getExpr().isAnalyzed) {
o.getExpr().analyzeImpl(analyzer);
}
}
}
// if return type is nested type, need to be determined the sub-element type
private void analyzeNestedFunction() {
// array
if (fnName.getFunction().equalsIgnoreCase("array")) {
if (children.size() > 0) {
this.type = new ArrayType(children.get(0).getType());
}
} else if (fnName.getFunction().equalsIgnoreCase("map")) {
if (children.size() > 1) {
this.type = new MapType(children.get(0).getType(), children.get(1).getType());
}
} else if (fnName.getFunction().equalsIgnoreCase("if")) {
if (children.get(1).getType().isArrayType() && (
((ArrayType) children.get(1).getType()).getItemType().isDecimalV3()
|| ((ArrayType) children.get(1)
.getType()).getItemType().isDecimalV2() || ((ArrayType) children.get(1)
.getType()).getItemType().isDatetimeV2())) {
this.type = children.get(1).getType();
}
} else if (fnName.getFunction().equalsIgnoreCase("named_struct")) {
ArrayList<StructField> newFields = Lists.newArrayList();
ArrayList<StructField> originFields = ((StructType) type).getFields();
for (int i = 0; i < children.size() && i + 1 < children.size(); i += 2) {
Type fieldType = originFields.get(i + i >> 2).getType();
if (fieldType.isDecimalV3() || fieldType.isDatetimeV2()) {
fieldType = children.get(i + 1).type;
}
StringLiteral nameLiteral = (StringLiteral) children.get(i);
newFields.add(new StructField(nameLiteral.getStringValue(), fieldType));
}
this.type = new StructType(newFields);
} else if (fnName.getFunction().equalsIgnoreCase("struct")) {
ArrayList<StructField> newFields = Lists.newArrayList();
ArrayList<StructField> originFields = ((StructType) type).getFields();
for (int i = 0; i < children.size(); i++) {
Type fieldType = originFields.get(i).getType();
if (originFields.get(i).getType().isDecimalV3() || originFields.get(i).getType().isDatetimeV2()) {
fieldType = children.get(i).type;
}
newFields.add(new StructField(fieldType));
}
this.type = new StructType(newFields);
} else if (fnName.getFunction().equalsIgnoreCase("topn_array")) {
this.type = new ArrayType(children.get(0).getType());
} else if (fnName.getFunction().equalsIgnoreCase("struct_element")) {
if (children.get(1) instanceof StringLiteral) {
String fieldName = children.get(1).getStringValue();
fn.setReturnType(((StructType) children.get(0).type).getField(fieldName).getType());
} else if (children.get(1) instanceof IntLiteral) {
int pos = (int) ((IntLiteral) children.get(1)).getValue();
fn.setReturnType(((StructType) children.get(0).type).getFields().get(pos - 1).getType());
}
this.type = fn.getReturnType();
} else if (fnName.getFunction().equalsIgnoreCase("array_distinct") || fnName.getFunction()
.equalsIgnoreCase("array_remove") || fnName.getFunction().equalsIgnoreCase("array_sort")
|| fnName.getFunction().equalsIgnoreCase("array_reverse_sort")
|| fnName.getFunction().equalsIgnoreCase("array_overlap")
|| fnName.getFunction().equalsIgnoreCase("array_union")
|| fnName.getFunction().equalsIgnoreCase("array_intersect")
|| fnName.getFunction().equalsIgnoreCase("array_compact")
|| fnName.getFunction().equalsIgnoreCase("array_slice")
|| fnName.getFunction().equalsIgnoreCase("array_popback")
|| fnName.getFunction().equalsIgnoreCase("array_popfront")
|| fnName.getFunction().equalsIgnoreCase("array_pushfront")
|| fnName.getFunction().equalsIgnoreCase("array_pushback")
|| fnName.getFunction().equalsIgnoreCase("reverse")
|| fnName.getFunction().equalsIgnoreCase("%element_slice%")
|| fnName.getFunction().equalsIgnoreCase("array_shuffle")
|| fnName.getFunction().equalsIgnoreCase("shuffle")
|| fnName.getFunction().equalsIgnoreCase("array_except")
|| fnName.getFunction().equalsIgnoreCase("array_concat")
|| fnName.getFunction().equalsIgnoreCase("array_apply")) {
if (children.size() > 0) {
this.type = children.get(0).getType();
}
} else if (fnName.getFunction().equalsIgnoreCase("array_zip")) {
// collect the child types to make a STRUCT type
Type[] childTypes = collectChildReturnTypes();
ArrayList<StructField> fields = new ArrayList<>();
for (int i = 0; i < childTypes.length; i++) {
fields.add(new StructField(((ArrayType) childTypes[i]).getItemType()));
}
this.type = new ArrayType(new StructType(fields));
}
if (this.type instanceof ArrayType) {
ArrayType arrayType = (ArrayType) type;
// Now Array type do not support ARRAY<NOT_NULL>, set it to true temporarily
boolean containsNull = true;
for (Expr child : children) {
Type childType = child.getType();
if (childType instanceof ArrayType) {
containsNull |= ((ArrayType) childType).getContainsNull();
}
}
arrayType.setContainsNull(containsNull);
}
}
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;
}
/**
* 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(Analyzer analyzer) 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);
// retExpr changed to original function, should be analyzed again.
retExpr.fn = null;
// reset children
retExpr.children.clear();
retExpr.children.addAll(oriExpr.getChildren());
retExpr.isRewrote = true;
retExpr.analyze(analyzer);
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;
}
public static FunctionCallExpr createMergeAggCall(
FunctionCallExpr agg, List<Expr> intermediateParams, List<Expr> realParams) {
Preconditions.checkState(agg.isAnalyzed);
Preconditions.checkState(agg.isAggregateFunction());
FunctionCallExpr result = new FunctionCallExpr(
agg.fnName, new FunctionParams(false, intermediateParams), true);
// Inherit the function object from 'agg'.
result.fn = agg.fn;
result.type = agg.type;
result.setAggFnParams(new FunctionParams(false, realParams));
return result;
}
public void readFields(DataInput in) throws IOException {
fnName = FunctionName.read(in);
fnParams = FunctionParams.read(in);
if (fnParams.exprs() != null) {
children.addAll(fnParams.exprs());
}
isAnalyticFnCall = in.readBoolean();
isMergeAggFn = in.readBoolean();
}
@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);
}
public static FunctionCallExpr read(DataInput in) throws IOException {
FunctionCallExpr func = new FunctionCallExpr();
func.readFields(in);
return func;
}
@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()));
}
@Override
public boolean haveFunction(String functionName) {
if (fnName.toString().equalsIgnoreCase(functionName)) {
return true;
}
return super.haveFunction(functionName);
}
public Function findUdf(FunctionName fnName, Analyzer analyzer) throws AnalysisException {
if (!analyzer.isUDFAllowed()) {
throw new AnalysisException(
"Does not support non-builtin functions, or function does not exist: "
+ this.toSqlImpl());
}
Function fn = null;
String dbName = null;
// when enable_udf_in_load == true, and db is null, maybe it's load, should find global function
if (!(Config.enable_udf_in_load && fnName.getDb() == null)) {
dbName = fnName.analyzeDb(analyzer);
}
if (!Strings.isNullOrEmpty(dbName)) {
// check operation privilege
if (!analyzer.isReplay() && !Env.getCurrentEnv().getAccessManager().checkDbPriv(ConnectContext.get(),
InternalCatalog.INTERNAL_CATALOG_NAME, dbName, PrivPredicate.SELECT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SELECT");
}
// TODO(gaoxin): ExternalDatabase not implement udf yet.
DatabaseIf db = Env.getCurrentEnv().getInternalCatalog().getDbNullable(dbName);
if (db != null && (db instanceof Database)) {
Function searchDesc = new Function(fnName, Arrays.asList(collectChildReturnTypes()),
Type.INVALID, false);
fn = ((Database) db).getFunction(searchDesc,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
}
}
// find from the internal database first, if not, then from the global functions
if (fn == null) {
Function searchDesc =
new Function(fnName, Arrays.asList(collectChildReturnTypes()), Type.INVALID, false);
fn = Env.getCurrentEnv().getGlobalFunctionMgr().getFunction(searchDesc,
Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
}
return fn;
}
// 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);
}
}