Expr.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/Expr.java
// and modified by Doris
package org.apache.doris.analysis;
import org.apache.doris.analysis.ArithmeticExpr.Operator;
import org.apache.doris.catalog.AggStateType;
import org.apache.doris.catalog.AggregateFunction;
import org.apache.doris.catalog.ArrayType;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.Function.NullableMode;
import org.apache.doris.catalog.FunctionSet;
import org.apache.doris.catalog.MapType;
import org.apache.doris.catalog.MaterializedIndexMeta;
import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.catalog.ScalarFunction;
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.common.Config;
import org.apache.doris.common.FeMetaVersion;
import org.apache.doris.common.FormatOptions;
import org.apache.doris.common.TreeNode;
import org.apache.doris.common.io.Text;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.persist.gson.GsonUtils;
import org.apache.doris.planner.normalize.Normalizer;
import org.apache.doris.qe.SessionVariable;
import org.apache.doris.rewrite.mvrewrite.MVExprEquivalent;
import org.apache.doris.statistics.ExprStats;
import org.apache.doris.thrift.TExpr;
import org.apache.doris.thrift.TExprNode;
import org.apache.doris.thrift.TExprOpcode;
import org.apache.doris.thrift.TFunctionBinaryType;
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.base.Suppliers;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.collections.CollectionUtils;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Root of the expr node hierarchy.
*/
public abstract class Expr extends TreeNode<Expr> implements ParseNode, Cloneable, ExprStats {
// Name of the function that needs to be implemented by every Expr that
// supports negation.
private static final String NEGATE_FN = "negate";
public static final String AGG_STATE_SUFFIX = "_state";
public static final String AGG_UNION_SUFFIX = "_union";
public static final String AGG_MERGE_SUFFIX = "_merge";
public static final String AGG_FOREACH_SUFFIX = "_foreach";
public static final String DEFAULT_EXPR_NAME = "expr";
protected boolean disableTableName = false;
protected boolean needExternalSql = false;
protected TableType tableType = null;
protected TableIf inputTable = null;
// to be used where we can't come up with a better estimate
public static final double DEFAULT_SELECTIVITY = 0.1;
public static final float FUNCTION_CALL_COST = 10;
protected Optional<Boolean> nullableFromNereids = Optional.empty();
// returns true if an Expr is a non-analytic aggregate.
private static final com.google.common.base.Predicate<Expr> IS_AGGREGATE_PREDICATE =
new com.google.common.base.Predicate<Expr>() {
public boolean apply(Expr arg) {
return arg instanceof FunctionCallExpr
&& ((FunctionCallExpr) arg).isAggregateFunction();
}
};
// Returns true if an Expr is a NOT CompoundPredicate.
public static final com.google.common.base.Predicate<Expr> IS_NOT_PREDICATE =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof CompoundPredicate
&& ((CompoundPredicate) arg).getOp() == CompoundPredicate.Operator.NOT;
}
};
// Returns true if an Expr is an OR CompoundPredicate.
public static final com.google.common.base.Predicate<Expr> IS_OR_PREDICATE =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof CompoundPredicate
&& ((CompoundPredicate) arg).getOp() == CompoundPredicate.Operator.OR;
}
};
// Returns true if an Expr is a scalar subquery
public static final com.google.common.base.Predicate<Expr> IS_SCALAR_SUBQUERY =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg.isScalarSubquery();
}
};
// Returns true if an Expr is an aggregate function that returns non-null on
// an empty set (e.g. count).
public static final com.google.common.base.Predicate<Expr> NON_NULL_EMPTY_AGG =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof FunctionCallExpr && ((FunctionCallExpr) arg).returnsNonNullOnEmpty();
}
};
// Returns true if an Expr is a builtin aggregate function.
public static final com.google.common.base.Predicate<Expr> CORRELATED_SUBQUERY_SUPPORT_AGG_FN =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
if (arg instanceof FunctionCallExpr) {
String fnName = ((FunctionCallExpr) arg).getFnName().getFunction();
return (fnName.equalsIgnoreCase("sum")
|| fnName.equalsIgnoreCase("max")
|| fnName.equalsIgnoreCase("min")
|| fnName.equalsIgnoreCase("avg")
|| fnName.equalsIgnoreCase(FunctionSet.COUNT));
} else {
return false;
}
}
};
public static final com.google.common.base.Predicate<Expr> IS_TRUE_LITERAL =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof BoolLiteral && ((BoolLiteral) arg).getValue();
}
};
public static final com.google.common.base.Predicate<Expr> IS_FALSE_LITERAL =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof BoolLiteral && !((BoolLiteral) arg).getValue();
}
};
public static final com.google.common.base.Predicate<Expr> IS_EQ_BINARY_PREDICATE =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return BinaryPredicate.getEqSlots(arg) != null;
}
};
public static final com.google.common.base.Predicate<Expr> IS_BINARY_PREDICATE =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof BinaryPredicate;
}
};
public static final com.google.common.base.Predicate<Expr> IS_NULL_LITERAL =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof NullLiteral;
}
};
public static final com.google.common.base.Predicate<Expr> IS_VARCHAR_SLOT_REF_IMPLICIT_CAST =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
// exclude explicit cast. for example: cast(k1 as date)
if (!arg.isImplicitCast()) {
return false;
}
List<Expr> children = arg.getChildren();
if (children.isEmpty()) {
return false;
}
Expr child = children.get(0);
return child instanceof SlotRef && child.getType().isVarchar();
}
};
public static final com.google.common.base.Predicate<Expr> IS_PLACRHOLDER =
new com.google.common.base.Predicate<Expr>() {
@Override
public boolean apply(Expr arg) {
return arg instanceof PlaceHolderExpr;
}
};
public void setSelectivity() {
selectivity = -1;
}
// id that's unique across the entire query statement and is assigned by
// Analyzer.registerConjuncts(); only assigned for the top-level terms of a
// conjunction, and therefore null for most Exprs
protected ExprId id;
// true if Expr is an auxiliary predicate that was generated by the plan generation
// process to facilitate predicate propagation;
// false if Expr originated with a query stmt directly
private boolean isAuxExpr = false;
@SerializedName("type")
protected Type type; // result of analysis
protected boolean isOnClauseConjunct; // set by analyzer
protected boolean isAnalyzed = false; // true after analyze() has been called
@SerializedName("opcode")
protected TExprOpcode opcode; // opcode for this expr
// estimated probability of a predicate evaluating to true;
// set during analysis;
// between 0 and 1 if valid: invalid: -1
protected double selectivity;
// estimated number of distinct values produced by Expr; invalid: -1
// set during analysis
protected long numDistinctValues;
protected int outputScale = -1;
protected boolean isFilter = false;
// The function to call. This can either be a scalar or aggregate function.
// Set in analyze().
protected Function fn;
// Cached value of IsConstant(), set during analyze() and valid if isAnalyzed_ is true.
private Supplier<Boolean> isConstant = Suppliers.memoize(() -> false);
// Flag to indicate whether to wrap this expr's toSql() in parenthesis. Set by parser.
// Needed for properly capturing expr precedences in the SQL string.
protected boolean printSqlInParens = false;
protected Optional<String> exprName = Optional.empty();
protected List<TupleId> boundTupleIds = null;
protected Expr() {
super();
type = Type.INVALID;
opcode = TExprOpcode.INVALID_OPCODE;
selectivity = -1.0;
numDistinctValues = -1;
}
protected Expr(Expr other) {
super();
id = other.id;
isAuxExpr = other.isAuxExpr;
type = other.type;
isAnalyzed = other.isAnalyzed;
selectivity = other.selectivity;
numDistinctValues = other.numDistinctValues;
opcode = other.opcode;
outputScale = other.outputScale;
isConstant = other.isConstant;
fn = other.fn;
printSqlInParens = other.printSqlInParens;
children = Expr.cloneList(other.children);
}
public boolean isAnalyzed() {
return isAnalyzed;
}
public void checkValueValid() throws AnalysisException {
}
public ExprId getId() {
return id;
}
public void setId(ExprId id) {
this.id = id;
}
// Name of expr, this is used by generating column name automatically when there is no
// alias or is not slotRef
public String getExprName() {
if (!this.exprName.isPresent()) {
this.exprName = Optional.of(Utils.normalizeName(this.getClass().getSimpleName(), DEFAULT_EXPR_NAME));
}
return this.exprName.get();
}
public Type getType() {
return type;
}
// add by cmy. for restoring
public void setType(Type type) {
this.type = type;
}
public TExprOpcode getOpcode() {
return opcode;
}
public double getSelectivity() {
return selectivity;
}
public boolean hasSelectivity() {
return selectivity >= 0;
}
public long getNumDistinctValues() {
return numDistinctValues;
}
public int getOutputScale() {
return outputScale;
}
public void setOutputScale(int scale) {
if (scale > 0 && scale < 10) {
outputScale = scale;
}
}
public boolean isFilter() {
return isFilter;
}
public boolean isOnClauseConjunct() {
return isOnClauseConjunct;
}
public void setIsOnClauseConjunct(boolean b) {
isOnClauseConjunct = b;
}
public boolean isAuxExpr() {
return isAuxExpr;
}
public void setIsAuxExpr() {
isAuxExpr = true;
}
public Function getFn() {
return fn;
}
public boolean getPrintSqlInParens() {
return printSqlInParens;
}
public void setPrintSqlInParens(boolean b) {
printSqlInParens = b;
}
/**
* Perform semantic analysis of node and all of its children.
* Throws exception if any errors found.
*/
public final void analyze(Analyzer analyzer) throws AnalysisException {
if (isAnalyzed()) {
return;
}
// Check the expr child limit.
if (children.size() > Config.expr_children_limit) {
throw new AnalysisException(String.format("Exceeded the maximum number of child "
+ "expressions (%d).", Config.expr_children_limit));
}
// analyzer may be null for certain literal constructions (e.g. IntLiteral).
if (analyzer != null) {
analyzer.incrementCallDepth();
// Check the expr depth limit. Do not print the toSql() to not overflow the stack.
if (analyzer.getCallDepth() > Config.expr_depth_limit) {
throw new AnalysisException(String.format("Exceeded the maximum depth of an "
+ "expression tree (%s).", Config.expr_depth_limit));
}
} else {
throw new AnalysisException("analyzer is null.");
}
for (Expr child : children) {
child.analyze(analyzer);
}
if (analyzer != null) {
analyzer.decrementCallDepth();
}
computeNumDistinctValues();
// Do all the analysis for the expr subclass before marking the Expr analyzed.
analyzeImpl(analyzer);
if (analyzer.safeIsEnableJoinReorderBasedCost()) {
setSelectivity();
}
analysisDone();
if (type.isAggStateType() && !(this instanceof SlotRef) && !children.get(0).getType().isAggStateType()) {
type = createAggStateType(((AggStateType) type), Arrays.asList(collectChildReturnTypes()),
Arrays.asList(collectChildReturnNullables()));
}
}
/**
* Does subclass-specific analysis. Subclasses should override analyzeImpl().
*/
protected abstract void analyzeImpl(Analyzer analyzer) throws AnalysisException;
/**
* Set the expr to be analyzed and computes isConstant_.
*/
protected void analysisDone() {
Preconditions.checkState(!isAnalyzed);
// We need to compute the const-ness as the last step, since analysis may change
// the result, e.g. by resolving function.
isConstant = Suppliers.memoize(this::isConstantImpl);
isAnalyzed = true;
}
protected void computeNumDistinctValues() {
if (isConstant()) {
numDistinctValues = 1;
} else {
// if this Expr contains slotrefs, we estimate the # of distinct values
// to be the maximum such number for any of the slotrefs;
// the subclass analyze() function may well want to override this, if it
// knows better
List<SlotRef> slotRefs = Lists.newArrayList();
this.collect(SlotRef.class, slotRefs);
numDistinctValues = -1;
for (SlotRef slotRef : slotRefs) {
numDistinctValues = Math.max(numDistinctValues, slotRef.numDistinctValues);
}
}
}
/**
* Collects the returns types of the child nodes in an array.
*/
protected Type[] collectChildReturnTypes() {
Type[] childTypes = new Type[children.size()];
for (int i = 0; i < children.size(); ++i) {
childTypes[i] = children.get(i).type;
}
return childTypes;
}
protected Boolean[] collectChildReturnNullables() {
Boolean[] childNullables = new Boolean[children.size()];
for (int i = 0; i < children.size(); ++i) {
childNullables[i] = children.get(i).isNullable();
}
return childNullables;
}
public List<Expr> getChildrenWithoutCast() {
List<Expr> result = new ArrayList<>();
for (int i = 0; i < children.size(); ++i) {
if (children.get(i) instanceof CastExpr) {
CastExpr castExpr = (CastExpr) children.get(i);
result.add(castExpr.getChild(0));
} else {
result.add(children.get(i));
}
}
return result;
}
public Expr getChildWithoutCast(int i) {
Preconditions.checkArgument(i < children.size(), "child index {0} out of range {1}", i, children.size());
Expr child = children.get(i);
return child instanceof CastExpr ? child.children.get(0) : child;
}
/**
* Helper function: analyze list of exprs
*
* @param exprs
* @param analyzer
* @throws AnalysisException
*/
public static void analyze(List<? extends Expr> exprs, Analyzer analyzer) throws AnalysisException {
for (Expr expr : exprs) {
expr.analyze(analyzer);
}
}
public static List<TExpr> treesToThrift(List<? extends Expr> exprs) {
List<TExpr> result = Lists.newArrayList();
for (Expr expr : exprs) {
result.add(expr.treeToThrift());
}
return result;
}
public static String debugString(List<? extends Expr> exprs) {
if (exprs == null || exprs.isEmpty()) {
return "";
}
List<String> strings = Lists.newArrayList();
for (Expr expr : exprs) {
strings.add(Strings.nullToEmpty(expr.debugString()));
}
return "(" + Joiner.on(" ").join(strings) + ")";
}
/**
* Return true if l1[i].equals(l2[i]) for all i.
*/
public static <C extends Expr> boolean equalLists(List<C> l1, List<C> l2) {
if (l1.size() != l2.size()) {
return false;
}
Iterator<C> l1Iter = l1.iterator();
Iterator<C> l2Iter = l2.iterator();
while (l1Iter.hasNext()) {
if (!l1Iter.next().equals(l2Iter.next())) {
return false;
}
}
return true;
}
/**
* Return true if l1 equals l2 when both lists are interpreted as sets.
*/
public static <C extends Expr> boolean equalSets(List<C> l1, List<C> l2) {
if (l1.size() != l2.size()) {
return false;
}
Map cMap1 = toCountMap(l1);
Map cMap2 = toCountMap(l2);
if (cMap1.size() != cMap2.size()) {
return false;
}
Iterator it = cMap1.keySet().iterator();
while (it.hasNext()) {
C obj = (C) it.next();
Integer count1 = (Integer) cMap1.get(obj);
Integer count2 = (Integer) cMap2.get(obj);
if (count2 == null || count1 != count2) {
return false;
}
}
return true;
}
public static <C extends Expr> HashMap<C, Integer> toCountMap(List<C> list) {
HashMap countMap = new HashMap<C, Integer>();
for (int i = 0; i < list.size(); i++) {
C obj = list.get(i);
Integer count = (Integer) countMap.get(obj);
if (count == null) {
countMap.put(obj, 1);
} else {
countMap.put(obj, count + 1);
}
}
return countMap;
}
public void analyzeNoThrow(Analyzer analyzer) {
try {
analyze(analyzer);
} catch (AnalysisException e) {
throw new IllegalStateException(e);
}
}
/**
* Create a deep copy of 'l'. If sMap is non-null, use it to substitute the
* elements of l.
*/
public static <C extends Expr> ArrayList<C> cloneList(List<C> l, ExprSubstitutionMap sMap) {
Preconditions.checkNotNull(l);
ArrayList<C> result = new ArrayList<C>();
for (C element : l) {
result.add((C) element.clone(sMap));
}
return result;
}
/**
* Create a deep copy of 'l'. If sMap is non-null, use it to substitute the
* elements of l.
*/
public static <C extends Expr> ArrayList<C> cloneList(List<C> l) {
Preconditions.checkNotNull(l);
ArrayList<C> result = new ArrayList<C>();
for (C element : l) {
result.add((C) element.clone());
}
return result;
}
public static <C extends Expr> ArrayList<C> cloneAndResetList(List<C> l) {
Preconditions.checkNotNull(l);
ArrayList<C> result = new ArrayList<C>();
for (C element : l) {
result.add((C) element.clone().reset());
}
return result;
}
/**
* Collect all unique Expr nodes of type 'cl' present in 'input' and add them to
* 'output' if they do not exist in 'output'.
* This can't go into TreeNode<>, because we'd be using the template param
* NodeType.
*/
public static <C extends Expr> void collectList(List<? extends Expr> input, Class<C> cl, List<C> output) {
Preconditions.checkNotNull(input);
for (Expr e : input) {
e.collect(cl, output);
}
}
/**
* get the expr which in l1 and l2 in the same time.
* Return the intersection of l1 and l2
*/
public static <C extends Expr> List<C> intersect(List<C> l1, List<C> l2) {
List<C> result = new ArrayList<C>();
for (C element : l1) {
if (l2.contains(element)) {
result.add(element);
}
}
return result;
}
/**
* Compute the intersection of l1 and l2, given the smap, and
* return the intersecting l1 elements in i1 and the intersecting l2 elements in i2.
* @throws AnalysisException
*/
public static void intersect(Analyzer analyzer,
List<Expr> l1, List<Expr> l2, ExprSubstitutionMap smap,
List<Expr> i1, List<Expr> i2) throws AnalysisException {
i1.clear();
i2.clear();
List<Expr> s1List = Expr.trySubstituteList(l1, smap, analyzer, false);
Preconditions.checkState(s1List.size() == l1.size());
List<Expr> s2List = Expr.trySubstituteList(l2, smap, analyzer, false);
Preconditions.checkState(s2List.size() == l2.size());
for (int i = 0; i < s1List.size(); ++i) {
Expr s1 = s1List.get(i);
for (int j = 0; j < s2List.size(); ++j) {
Expr s2 = s2List.get(j);
if (s1.equals(s2)) {
i1.add(l1.get(i));
i2.add(l2.get(j));
break;
}
}
}
}
/**
* Returns true if the list contains an aggregate expr.
*/
public static <C extends Expr> boolean containsAggregate(List<? extends Expr> input) {
for (Expr e : input) {
if (e.containsAggregate()) {
return true;
}
}
return false;
}
public static void extractSlots(Expr root, Set<SlotId> slotIdSet) {
if (root instanceof SlotRef) {
slotIdSet.add(((SlotRef) root).getDesc().getId());
return;
}
for (Expr child : root.getChildren()) {
extractSlots(child, slotIdSet);
}
}
/**
* Returns an analyzed clone of 'this' with exprs substituted according to smap.
* Removes implicit casts and analysis state while cloning/substituting exprs within
* this tree, such that the returned result has minimal implicit casts and types.
* Throws if analyzing the post-substitution expr tree failed.
* If smap is null, this function is equivalent to clone().
* If preserveRootType is true, the resulting expr tree will be cast if necessary to
* the type of 'this'.
*/
public Expr trySubstitute(ExprSubstitutionMap smap, Analyzer analyzer,
boolean preserveRootType) throws AnalysisException {
return trySubstitute(smap, null, analyzer, preserveRootType);
}
public Expr trySubstitute(ExprSubstitutionMap smap, ExprSubstitutionMap disjunctsMap, Analyzer analyzer,
boolean preserveRootType) throws AnalysisException {
Expr result = clone();
if (result instanceof PlaceHolderExpr) {
return result;
}
// Return clone to avoid removing casts.
if (smap == null) {
return result;
}
result = result.substituteImpl(smap, disjunctsMap, analyzer);
result.analyze(analyzer);
if (preserveRootType && !type.equals(result.getType())) {
result = result.castTo(type);
}
return result;
}
/**
* Returns an analyzed clone of 'this' with exprs substituted according to smap.
* Removes implicit casts and analysis state while cloning/substituting exprs within
* this tree, such that the returned result has minimal implicit casts and types.
* Expects the analysis of the post-substitution expr to succeed.
* If smap is null, this function is equivalent to clone().
* If preserveRootType is true, the resulting expr tree will be cast if necessary to
* the type of 'this'.
*
* @throws AnalysisException
*/
public Expr substitute(ExprSubstitutionMap smap, Analyzer analyzer, boolean preserveRootType)
throws AnalysisException {
return substitute(smap, null, analyzer, preserveRootType);
}
public Expr substitute(ExprSubstitutionMap smap, ExprSubstitutionMap disjunctsMap,
Analyzer analyzer, boolean preserveRootType)
throws AnalysisException {
try {
return trySubstitute(smap, disjunctsMap, analyzer, preserveRootType);
} catch (AnalysisException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Failed analysis after expr substitution.", e);
}
}
public static ArrayList<Expr> trySubstituteList(Iterable<? extends Expr> exprs,
ExprSubstitutionMap smap, Analyzer analyzer, boolean preserveRootTypes)
throws AnalysisException {
if (exprs == null) {
return null;
}
ArrayList<Expr> result = new ArrayList<Expr>();
for (Expr e : exprs) {
result.add(e.trySubstitute(smap, analyzer, preserveRootTypes));
}
return result;
}
public static ArrayList<Expr> substituteList(
Iterable<? extends Expr> exprs,
ExprSubstitutionMap smap, Analyzer analyzer, boolean preserveRootTypes) {
try {
return trySubstituteList(exprs, smap, analyzer, preserveRootTypes);
} catch (Exception e) {
throw new IllegalStateException("Failed analysis after expr substitution: " + e.getMessage());
}
}
/**
* Recursive method that performs the actual substitution for try/substitute() while
* removing implicit casts. Resets the analysis state in all non-SlotRef expressions.
* Exprs that have non-child exprs which should be affected by substitutions must
* override this method and apply the substitution to such exprs as well.
*/
protected Expr substituteImpl(ExprSubstitutionMap smap, ExprSubstitutionMap disjunctsMap, Analyzer analyzer) {
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) {
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.
if (!(this instanceof SlotRef)) {
resetAnalysisState();
}
return this;
}
/**
* Removes duplicate exprs (according to equals()).
*/
public static <C extends Expr> void removeDuplicates(List<C> l) {
if (l == null) {
return;
}
ListIterator<C> it1 = l.listIterator();
while (it1.hasNext()) {
C e1 = it1.next();
ListIterator<C> it2 = l.listIterator();
boolean duplicate = false;
while (it2.hasNext()) {
C e2 = it2.next();
if (e1 == e2) {
// only check up to but excluding e1
break;
}
if (e1.equals(e2)) {
duplicate = true;
break;
}
}
if (duplicate) {
it1.remove();
}
}
}
public static boolean isBound(List<? extends Expr> exprs, List<TupleId> tids) {
for (Expr expr : exprs) {
if (!expr.isBoundByTupleIds(tids)) {
return false;
}
}
return true;
}
public static <C extends Expr> void getIds(List<? extends Expr> exprs, List<TupleId> tupleIds,
List<SlotId> slotIds) {
if (exprs == null) {
return;
}
for (Expr e : exprs) {
e.getIds(tupleIds, slotIds);
}
}
public void markAgg() {
for (Expr child : children) {
child.markAgg();
}
}
/**
* Returns the product of the given exprs' number of distinct values or -1 if any of
* the exprs have an invalid number of distinct values.
*/
public static long getNumDistinctValues(List<Expr> exprs) {
if (exprs == null || exprs.isEmpty()) {
return 0;
}
long numDistinctValues = 1;
for (Expr expr : exprs) {
if (expr.getNumDistinctValues() == -1) {
numDistinctValues = -1;
break;
}
numDistinctValues *= expr.getNumDistinctValues();
}
return numDistinctValues;
}
public String toSql() {
return (printSqlInParens) ? "(" + toSqlImpl() + ")" : toSqlImpl();
}
public void setDisableTableName(boolean value) {
disableTableName = value;
for (Expr child : children) {
child.setDisableTableName(value);
}
}
public void setExternalContext(boolean needExternalSql, TableType tableType, TableIf inputTable) {
this.needExternalSql = needExternalSql;
this.tableType = tableType;
this.inputTable = inputTable;
for (Expr child : children) {
child.setExternalContext(needExternalSql, tableType, inputTable);
}
}
public String toSqlWithoutTbl() {
setDisableTableName(true);
String result = toSql();
setDisableTableName(false);
return result;
}
public String toDigest() {
return (printSqlInParens) ? "(" + toDigestImpl() + ")" : toDigestImpl();
}
/**
* Returns a SQL string representing this expr. Subclasses should override this method
* instead of toSql() to ensure that parenthesis are properly added around the toSql().
*/
protected abstract String toSqlImpl();
/**
* !!!!!! Important !!!!!!
* Subclasses should override this method if
* sql digest should be represented different from tosqlImpl().
*/
protected String toDigestImpl() {
return toSqlImpl();
}
public String toExternalSql(TableType tableType, TableIf table) {
setExternalContext(true, tableType, table);
String result = toSql();
setExternalContext(false, null, null);
return result;
}
/**
* Return a column label for the expression
*/
public String toColumnLabel() {
return toSql();
}
public List<String> toSubColumnLabel() {
return Lists.newArrayList();
}
// Convert this expr, including all children, to its Thrift representation.
public TExpr treeToThrift() {
TExpr result = new TExpr();
treeToThriftHelper(result);
return result;
}
public void setFn(Function fn) {
this.fn = fn;
}
protected void treeToThriftHelper(TExpr container) {
treeToThriftHelper(container, ((expr, exprNode) -> expr.toThrift(exprNode)));
}
// Append a flattened version of this expr, including all children, to 'container'.
protected void treeToThriftHelper(TExpr container, ExprVisitor visitor) {
TExprNode msg = new TExprNode();
msg.type = type.toThrift();
msg.num_children = children.size();
if (fn != null) {
msg.setFn(fn.toThrift(type, collectChildReturnTypes(), collectChildReturnNullables()));
if (fn.hasVarArgs()) {
msg.setVarargStartIdx(fn.getNumArgs() - 1);
}
}
msg.output_scale = getOutputScale();
msg.setIsNullable(nullableFromNereids.isPresent() ? nullableFromNereids.get() : isNullable());
visitor.visit(this, msg);
container.addToNodes(msg);
for (Expr child : children) {
child.treeToThriftHelper(container, visitor);
}
}
public interface ExprVisitor {
void visit(Expr expr, TExprNode exprNode);
}
public static Type getAssignmentCompatibleType(List<Expr> children) {
Type assignmentCompatibleType = Type.INVALID;
for (int i = 0; i < children.size()
&& (assignmentCompatibleType.isDecimalV3() || assignmentCompatibleType.isDatetimeV2()
|| assignmentCompatibleType.isInvalid()); i++) {
if (children.get(i) instanceof NullLiteral) {
continue;
}
assignmentCompatibleType = assignmentCompatibleType.isInvalid() ? children.get(i).type
: ScalarType.getAssignmentCompatibleType(assignmentCompatibleType, children.get(i).type,
true, SessionVariable.getEnableDecimal256());
}
return assignmentCompatibleType;
}
// Convert this expr into msg (excluding children), which requires setting
// msg.op as well as the expr-specific field.
protected abstract void toThrift(TExprNode msg);
public List<String> childrenToSql() {
List<String> result = Lists.newArrayList();
for (Expr child : children) {
result.add(child.toSql());
}
return result;
}
// public void printChild() {
// LOG.debug("PALOTEST toSql={} debugString={}",
// this.toSql(),
// this.debugString());
// if (this instanceof SlotRef) {
// LOG.debug("dhc slot slotid={} tupleid={}",
// ((SlotRef) this).getSlotId(),
// ((SlotRef) this).getDesc().getParent().getId());
// }
// }
public List<String> childrenToDigest() {
List<String> childrenDigestList = Lists.newArrayList();
for (Expr child : children) {
childrenDigestList.add(child.toDigest());
}
return childrenDigestList;
}
public static com.google.common.base.Predicate<Expr> isAggregatePredicate() {
return IS_AGGREGATE_PREDICATE;
}
public boolean isAggregate() {
return IS_AGGREGATE_PREDICATE.apply(this);
}
public String debugString() {
return debugString(children);
}
/**
* Resets the internal state of this expr produced by analyze().
* Only modifies this expr, and not its child exprs.
*/
protected void resetAnalysisState() {
isAnalyzed = false;
}
/**
* Resets the internal analysis state of this expr tree. Removes implicit casts.
*/
public Expr reset() {
if (isImplicitCast()) {
return getChild(0).reset();
}
for (int i = 0; i < children.size(); ++i) {
children.set(i, children.get(i).reset());
}
resetAnalysisState();
return this;
}
public static ArrayList<Expr> resetList(ArrayList<Expr> l) {
for (int i = 0; i < l.size(); ++i) {
l.set(i, l.get(i).reset());
}
return l;
}
/**
* Creates a deep copy of this expr including its analysis state. The method is
* abstract in this class to force new Exprs to implement it.
*/
@Override
public abstract Expr clone();
public boolean notCheckDescIdEquals(Object obj) {
return equals(obj);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != this.getClass()) {
return false;
}
// don't compare type, this could be called pre-analysis
Expr expr = (Expr) obj;
if (children.size() != expr.children.size()) {
return false;
}
for (int i = 0; i < children.size(); ++i) {
if (!children.get(i).equals(expr.children.get(i))) {
return false;
}
}
if (fn == null && expr.fn == null) {
return true;
}
if (fn == null || expr.fn == null) {
return false;
}
// Both fn_'s are not null
return fn.equals(expr.fn);
}
@Override
public int hashCode() {
// in group by clause, group by list need to remove duplicate exprs, the expr may be not not analyzed, the id
// may be null
if (id == null) {
int result = 31 * Objects.hashCode(type) + Objects.hashCode(opcode);
for (Expr child : children) {
result = 31 * result + Objects.hashCode(child);
}
return result;
}
return id.asInt();
}
/**
* Gather conjuncts from this expr and return them in a list.
* A conjunct is an expr that returns a boolean, e.g., Predicates, function calls,
* SlotRefs, etc. Hence, this method is placed here and not in Predicate.
*/
public List<Expr> getConjuncts() {
List<Expr> list = Lists.newArrayList();
if (this instanceof CompoundPredicate
&& ((CompoundPredicate) this).getOp() == CompoundPredicate.Operator.AND) {
// TODO: we have to convert CompoundPredicate.AND to two expr trees for
// conjuncts because NULLs are handled differently for CompoundPredicate.AND
// and conjunct evaluation. This is not optimal for jitted exprs because it
// will result in two functions instead of one. Create a new CompoundPredicate
// Operator (i.e. CONJUNCT_AND) with the right NULL semantics and use that
// instead
list.addAll((getChild(0)).getConjuncts());
list.addAll((getChild(1)).getConjuncts());
} else {
list.add(this);
}
return list;
}
/**
* Create a deep copy of 'this'. If sMap is non-null,
* use it to substitute 'this' or its subnodes.
* <p/>
* Expr subclasses that add non-value-type members must override this.
*/
public Expr clone(ExprSubstitutionMap sMap) {
if (sMap != null) {
for (int i = 0; i < sMap.getLhs().size(); ++i) {
if (this.equals(sMap.getLhs().get(i))) {
return sMap.getRhs().get(i).clone(null);
}
}
}
Expr result = (Expr) this.clone();
result.children = Lists.newArrayList();
for (Expr child : children) {
result.children.add(((Expr) child).clone(sMap));
}
return result;
}
// Identical behavior to TreeNode.collect() except it matches expr that are aggregates
public <C extends Expr> void collectAggregateExprs(List<C> output) {
if (isAggregate() && !output.contains((C) this)) {
output.add((C) this);
return;
}
for (Expr child : children) {
child.collectAggregateExprs(output);
}
}
public boolean containsAggregate() {
if (isAggregate()) {
return true;
}
return containsAggregate(children);
}
/**
* Return 'this' with all sub-exprs substituted according to
* sMap. Ids of 'this' and its children are retained.
*/
public Expr substitute(ExprSubstitutionMap sMap) {
Preconditions.checkNotNull(sMap);
for (int i = 0; i < sMap.getLhs().size(); ++i) {
if (this.equals(sMap.getLhs().get(i))) {
Expr result = sMap.getRhs().get(i).clone(null);
if (id != null) {
result.id = id;
}
return result;
}
}
for (int i = 0; i < children.size(); ++i) {
children.set(i, children.get(i).substitute(sMap));
}
return this;
}
/**
* Returns true if expr is fully bound by tid, otherwise false.
*/
public boolean isBound(TupleId tid) {
return isBoundByTupleIds(Lists.newArrayList(tid));
}
/**
* Returns true if expr is fully bound by tids, otherwise false.
*/
public boolean isBoundByTupleIds(List<TupleId> tids) {
if (boundTupleIds != null && !boundTupleIds.isEmpty()) {
return boundTupleIds.stream().anyMatch(id -> tids.contains(id));
}
for (Expr child : children) {
if (!child.isBoundByTupleIds(tids)) {
return false;
}
}
return true;
}
public void setBoundTupleIds(List<TupleId> tids) {
boundTupleIds = tids;
}
/**
* Returns true if expr have child bound by tids, otherwise false.
*/
public boolean isRelativedByTupleIds(List<TupleId> tids) {
for (Expr child : children) {
if (child.isRelativedByTupleIds(tids)) {
return true;
}
}
return false;
}
/**
* Returns true if expr is fully bound by slotId, otherwise false.
*/
public boolean isBound(SlotId slotId) {
for (Expr child : children) {
if (!child.isBound(slotId)) {
return false;
}
}
return true;
}
public boolean isBound(List<SlotId> slotIds) {
final List<TupleId> exprTupleIds = Lists.newArrayList();
final List<SlotId> exprSlotIds = Lists.newArrayList();
getIds(exprTupleIds, exprSlotIds);
return !exprSlotIds.retainAll(slotIds);
}
// Get all the slotRefs that are bound to the tupleIds.
// As long as it is bound to one tuple
// Recursively call all levels of expr
public void getSlotRefsBoundByTupleIds(List<TupleId> tupleIds, Set<SlotRef> boundSlotRefs) {
for (Expr child : children) {
child.getSlotRefsBoundByTupleIds(tupleIds, boundSlotRefs);
}
}
public void getIds(List<TupleId> tupleIds, List<SlotId> slotIds) {
for (Expr child : children) {
child.getIds(tupleIds, slotIds);
}
}
public Expr getRealSlotRef() {
return this;
}
public Map<Long, Set<String>> getTableIdToColumnNames() {
Map<Long, Set<String>> tableIdToColumnNames = new HashMap<Long, Set<String>>();
getTableIdToColumnNames(tableIdToColumnNames);
return tableIdToColumnNames;
}
public void getTableIdToColumnNames(Map<Long, Set<String>> tableIdToColumnNames) {
Preconditions.checkState(tableIdToColumnNames != null);
for (Expr child : children) {
child.getTableIdToColumnNames(tableIdToColumnNames);
}
}
/**
* @return true if this is an instance of LiteralExpr
*/
public boolean isLiteral() {
return this instanceof LiteralExpr;
}
/**
* Returns true if this expression should be treated as constant. I.e. if the frontend
* and backend should assume that two evaluations of the expression within a query will
* return the same value. Examples of constant expressions include:
* - Literal values like 1, "foo", or NULL
* - Deterministic operators applied to constant arguments, e.g. 1 + 2, or
* concat("foo", "bar")
* - Functions that should be always return the same value within a query but may
* return different values for different queries. E.g. now(), which we want to
* evaluate only once during planning.
* May incorrectly return true if the expression is not analyzed.
* TODO: isAnalyzed_ should be a precondition for isConstant(), since it is not always
* possible to correctly determine const-ness before analysis (e.g. see
* FunctionCallExpr.isConstant()).
*/
public final boolean isConstant() {
if (isAnalyzed) {
return isConstant.get();
}
return isConstantImpl();
}
/**
* Implements isConstant() - computes the value without using 'isConstant_'.
*/
protected boolean isConstantImpl() {
for (Expr expr : children) {
if (!expr.isConstant()) {
return false;
}
}
return true;
}
protected void compactForLiteral(Type type) throws AnalysisException {
for (Expr expr : children) {
expr.compactForLiteral(type);
}
}
/**
* Return true if this expr is a scalar subquery.
*/
public boolean isScalarSubquery() {
Preconditions.checkState(isAnalyzed);
return this instanceof Subquery && getType().isScalarType();
}
/**
* Checks whether this expr returns a boolean type or NULL type.
* If not, throws an AnalysisException with an appropriate error message using
* 'name' as a prefix. For example, 'name' could be "WHERE clause".
* The error message only contains this.toSql() if printExpr is true.
*/
public void checkReturnsBool(String name, boolean printExpr) throws AnalysisException {
if (!type.isBoolean() && !type.isNull()) {
if (this instanceof BoolLiteral) {
return;
}
throw new AnalysisException(
String.format("%s%s requires return type 'BOOLEAN'. " + "Actual type is '%s'.", name,
(printExpr) ? " '" + toSql() + "'" : "", type.toString()));
}
}
/**
* Checks whether comparing predicates' children include bitmap type.
*/
public void checkIncludeBitmap() throws AnalysisException {
for (int i = 0; i < children.size(); ++i) {
if (children.get(i).getType().isBitmapType()) {
throw new AnalysisException("Unsupported bitmap type in expression: " + toSql());
}
}
}
public Expr checkTypeCompatibility(Type targetType) throws AnalysisException {
if (!targetType.isComplexType() && !targetType.isAggStateType()
&& targetType.getPrimitiveType() == type.getPrimitiveType()) {
if (targetType.isDecimalV2() && type.isDecimalV2()) {
return this;
} else if (!PrimitiveType.typeWithPrecision.contains(type.getPrimitiveType())) {
return this;
} else if (((ScalarType) targetType).decimalScale() == ((ScalarType) type).decimalScale()
&& ((ScalarType) targetType).decimalPrecision() == ((ScalarType) type).decimalPrecision()) {
return this;
}
}
if (type.isAggStateType() != targetType.isAggStateType()) {
throw new AnalysisException("AggState can't cast from other type.");
}
// bitmap must match exactly
if (targetType.getPrimitiveType() == PrimitiveType.BITMAP) {
throw new AnalysisException("bitmap column require the function return type is BITMAP");
}
// TODO(weixiang): why bitmap is so strict but hll is not strict, may be bitmap can be same to hll
// here `quantile_state` is also strict now. may be can be same to hll too.
if (targetType.getPrimitiveType() == PrimitiveType.QUANTILE_STATE) {
throw new AnalysisException("quantile_state column require the function return type is QUANTILE_STATE");
}
// TargetTable's hll column must be hll_hash's result
if (targetType.getPrimitiveType() == PrimitiveType.HLL) {
checkHllCompatibility();
return this;
}
Expr newExpr = castTo(targetType);
newExpr.checkValueValid();
return newExpr;
}
private void checkHllCompatibility() throws AnalysisException {
final String hllMismatchLog = "Column's type is HLL,"
+ " SelectList must contains HLL or hll_hash or hll_empty function's result";
if (this instanceof SlotRef) {
final SlotRef slot = (SlotRef) this;
if (!slot.getType().equals(Type.HLL)) {
throw new AnalysisException(hllMismatchLog);
}
} else if (this instanceof FunctionCallExpr) {
final FunctionCallExpr functionExpr = (FunctionCallExpr) this;
if (!functionExpr.getFnName().getFunction().equalsIgnoreCase("hll_hash")
&& !functionExpr.getFnName().getFunction().equalsIgnoreCase("hll_empty")) {
throw new AnalysisException(hllMismatchLog);
}
} else {
throw new AnalysisException(hllMismatchLog);
}
}
/**
* Checks validity of cast, and
* calls uncheckedCastTo() to
* create a cast expression that casts
* this to a specific type.
*
* @param targetType type to be cast to
* @return cast expression, or converted literal,
* should never return null
* @throws AnalysisException when an invalid cast is asked for, for example,
* failure to convert a string literal to a date literal
*/
public final Expr castTo(Type targetType) throws AnalysisException {
if (this instanceof PlaceHolderExpr && this.type.isUnsupported()) {
return this;
}
// If the targetType is NULL_TYPE then ignore the cast because NULL_TYPE
// is compatible with all types and no cast is necessary.
if (targetType.isNull()) {
return this;
}
if (this.type.isAggStateType()) {
List<Type> subTypes = ((AggStateType) targetType).getSubTypes();
List<Boolean> subNullables = ((AggStateType) targetType).getSubTypeNullables();
if (this instanceof FunctionCallExpr) {
if (subTypes.size() != getChildren().size()) {
throw new AnalysisException("AggState's subTypes size not euqal to children number");
}
for (int i = 0; i < subTypes.size(); i++) {
setChild(i, getChild(i).castTo(subTypes.get(i)));
if (getChild(i).isNullable() && !subNullables.get(i)) {
FunctionCallExpr newChild = new FunctionCallExpr("non_nullable",
Lists.newArrayList(getChild(i)));
newChild.analyzeImplForDefaultValue(subTypes.get(i));
setChild(i, newChild);
} else if (!getChild(i).isNullable() && subNullables.get(i)) {
FunctionCallExpr newChild = new FunctionCallExpr("nullable", Lists.newArrayList(getChild(i)));
newChild.analyzeImplForDefaultValue(subTypes.get(i));
setChild(i, newChild);
}
}
type = targetType;
} else {
List<Type> selfSubTypes = ((AggStateType) type).getSubTypes();
if (subTypes.size() != selfSubTypes.size()) {
throw new AnalysisException("AggState's subTypes size did not match");
}
for (int i = 0; i < subTypes.size(); i++) {
if (subTypes.get(i) != selfSubTypes.get(i)) {
throw new AnalysisException("AggState's subType did not match");
}
}
}
} else {
if (this.type.equals(targetType)) {
return this;
}
}
if (targetType.getPrimitiveType() == PrimitiveType.DECIMALV2
&& this.type.getPrimitiveType() == PrimitiveType.DECIMALV2) {
this.type = targetType;
return this;
}
if (this.type.isStringType() && targetType.isStringType()) {
return this;
}
// Preconditions.checkState(PrimitiveType.isImplicitCast(type, targetType),
// "cast %s to %s", this.type, targetType);
// TODO(zc): use implicit cast
if (!Type.canCastTo(this.type, targetType)) {
throw new AnalysisException("can not cast from origin type " + this.type
+ " to target type=" + targetType);
}
return uncheckedCastTo(targetType);
}
/**
* Create an expression equivalent to 'this' but returning targetType;
* possibly by inserting an implicit cast,
* or by returning an altogether new expression
*
* @param targetType type to be cast to
* @return cast expression, or converted literal,
* should never return null
* @throws AnalysisException when an invalid cast is asked for, for example,
* failure to convert a string literal to a date literal
*/
protected Expr uncheckedCastTo(Type targetType) throws AnalysisException {
return new CastExpr(targetType, this);
}
/**
* Add a cast expression above child.
* If child is a literal expression, we attempt to
* convert the value of the child directly, and not insert a cast node.
*
* @param targetType type to be cast to
* @param childIndex index of child to be cast
*/
public void castChild(Type targetType, int childIndex) throws AnalysisException {
Expr child = getChild(childIndex);
Expr newChild = child.castTo(targetType);
setChild(childIndex, newChild);
}
/**
* Assuming it can cast to the targetType, use for convert literal value to
* target type without a cast node.
*/
public static Expr convertLiteral(Expr expr, Type targetType) throws AnalysisException {
if (!(expr instanceof LiteralExpr)) {
return expr;
}
Expr newExpr = expr.uncheckedCastTo(targetType);
if (newExpr instanceof CastExpr) {
return ((LiteralExpr) newExpr.getChild(0)).convertTo(targetType);
}
return newExpr;
}
/**
* Add a cast expression above child.
* If child is a literal expression, we attempt to
* convert the value of the child directly, and not insert a cast node.
*
* @param targetType type to be cast to
* @param childIndex index of child to be cast
*/
public void uncheckedCastChild(Type targetType, int childIndex)
throws AnalysisException {
Expr child = getChild(childIndex);
//avoid to generate Expr like cast (cast(... as date) as date)
if (!child.getType().equals(targetType)) {
Expr newChild = child.uncheckedCastTo(targetType);
setChild(childIndex, newChild);
}
}
/**
* Returns child expr if this expr is an implicit cast, otherwise returns 'this'.
*/
public Expr ignoreImplicitCast() {
return this;
}
/**
* Cast the operands of a binary operation as necessary,
* give their compatible type.
* String literals are converted first, to enable casting of the
* the other non-string operand.
*
* @param compatibleType
* @return The possibly changed compatibleType
* (if a string literal forced casting the other operand)
*/
public Type castBinaryOp(Type compatibleType) throws AnalysisException {
Preconditions.checkState(this instanceof BinaryPredicate || this instanceof ArithmeticExpr);
Type t1 = getChild(0).getType();
Type t2 = getChild(1).getType();
// add operand casts
Preconditions.checkState(compatibleType.isValid());
if (t1.isDecimalV3() || t1.isDecimalV2()) {
if (!t1.equals(compatibleType)) {
castChild(compatibleType, 0);
}
} else if (t1.getPrimitiveType() != compatibleType.getPrimitiveType()) {
castChild(compatibleType, 0);
}
if (t2.isDecimalV3() || t2.isDecimalV2()) {
if (!t2.equals(compatibleType)) {
castChild(compatibleType, 1);
}
} else if (t2.getPrimitiveType() != compatibleType.getPrimitiveType()) {
castChild(compatibleType, 1);
}
return compatibleType;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this.getClass()).add("id", id).add("type", type).add("sel",
selectivity).add("#distinct", numDistinctValues).add("scale", outputScale).toString();
}
/**
* This method is mainly used to find the original column corresponding to the current expr.
* Find the initial slotRef from the current slot ref.
*
* If the initial expr is not a slotRef, it returns null directly.
* If the current slotRef comes from another expression transformation,
* rather than directly from another slotRef, null will also be returned.
*/
public SlotRef getSrcSlotRef() {
SlotRef unwrapSloRef = this.unwrapSlotRef();
if (unwrapSloRef == null) {
return null;
}
SlotDescriptor slotDescriptor = unwrapSloRef.getDesc();
if (slotDescriptor == null) {
return null;
}
List<Expr> sourceExpr = slotDescriptor.getSourceExprs();
if (sourceExpr == null || sourceExpr.isEmpty()) {
return unwrapSloRef;
}
if (sourceExpr.size() > 1) {
return null;
}
return sourceExpr.get(0).getSrcSlotRef();
}
// same as getSrcSlotRef, but choose first expr if has multiple src exprs
// only used in RewriteInPredicateRule
public SlotRef tryGetSrcSlotRef() {
SlotRef unwrapSloRef = this.unwrapSlotRef();
if (unwrapSloRef == null) {
return null;
}
SlotDescriptor slotDescriptor = unwrapSloRef.getDesc();
if (slotDescriptor == null) {
return null;
}
List<Expr> sourceExpr = slotDescriptor.getSourceExprs();
if (sourceExpr == null || sourceExpr.isEmpty()) {
return unwrapSloRef;
}
return sourceExpr.get(0).tryGetSrcSlotRef();
}
public boolean comeFrom(Expr srcExpr) {
SlotRef unwrapSloRef = this.unwrapSlotRef();
if (unwrapSloRef == null) {
return false;
}
SlotRef unwrapSrcSlotRef = srcExpr.unwrapSlotRef();
if (unwrapSrcSlotRef == null) {
return false;
}
if (unwrapSloRef.columnEqual(unwrapSrcSlotRef)) {
return true;
}
// check source expr
SlotDescriptor slotDescriptor = unwrapSloRef.getDesc();
if (slotDescriptor == null || slotDescriptor.getSourceExprs() == null
|| slotDescriptor.getSourceExprs().size() != 1) {
return false;
}
return slotDescriptor.getSourceExprs().get(0).comeFrom(unwrapSrcSlotRef);
}
/**
* If 'this' is a SlotRef or a Cast that wraps a SlotRef, returns that SlotRef.
* Otherwise returns null.
*/
public SlotRef unwrapSlotRef() {
if (this instanceof SlotRef) {
return (SlotRef) this;
} else if (this instanceof CastExpr && getChild(0) instanceof SlotRef) {
return (SlotRef) getChild(0);
} else {
return null;
}
}
/**
* If 'this' is a SlotRef or a Cast that wraps a SlotRef, returns that SlotRef.
* Otherwise returns null.
*/
public SlotRef unwrapSlotRef(boolean implicitOnly) {
Expr unwrappedExpr = unwrapExpr(implicitOnly);
if (unwrappedExpr instanceof SlotRef) {
return (SlotRef) unwrappedExpr;
}
return null;
}
/**
* Returns the first child if this Expr is a CastExpr. Otherwise, returns 'this'.
*/
public Expr unwrapExpr(boolean implicitOnly) {
if (this instanceof CastExpr
&& (!implicitOnly || ((CastExpr) this).isImplicit())) {
return children.get(0);
}
return this;
}
/**
* Returns the descriptor of the scan slot that directly or indirectly produces
* the values of 'this' SlotRef. Traverses the source exprs of intermediate slot
* descriptors to resolve materialization points (e.g., aggregations).
* Returns null if 'e' or any source expr of 'e' is not a SlotRef or cast SlotRef.
*/
public SlotDescriptor findSrcScanSlot() {
SlotRef slotRef = unwrapSlotRef(false);
if (slotRef == null) {
return null;
}
SlotDescriptor slotDesc = slotRef.getDesc();
if (slotDesc.isScanSlot()) {
return slotDesc;
}
if (slotDesc.getSourceExprs().size() == 1) {
return slotDesc.getSourceExprs().get(0).findSrcScanSlot();
}
// No known source expr, or there are several source exprs meaning the slot is
// has no single source table.
return null;
}
public static double getConstFromExpr(Expr e) throws AnalysisException {
Preconditions.checkState(e.isConstant());
double value = 0;
if (e instanceof LiteralExpr) {
LiteralExpr lit = (LiteralExpr) e;
value = lit.getDoubleValue();
} else {
throw new AnalysisException("To const value not a LiteralExpr ");
}
return value;
}
public boolean isImplicitCast() {
return this instanceof CastExpr && ((CastExpr) this).isImplicit();
}
public boolean contains(Expr expr) {
if (this.equals(expr)) {
return true;
}
for (Expr child : getChildren()) {
if (child.contains(expr)) {
return true;
}
}
return false;
}
public boolean contains(List<Expr> exprs) {
if (exprs.isEmpty()) {
return false;
}
for (Expr expr : exprs) {
if (contains(expr)) {
return true;
}
}
return false;
}
public Expr findEqual(List<Expr> exprs) {
if (exprs.isEmpty()) {
return null;
}
for (Expr expr : exprs) {
if (contains(expr)) {
return expr;
}
}
return null;
}
/*
* this function is used be lambda function to find lambda argument.
* and replace a new ColumnRefExpr
*/
public void replaceExpr(String colName, ColumnRefExpr slotRefs, List<Expr> slotExpr) {
for (int i = 0; i < children.size(); ++i) {
children.get(i).replaceExpr(colName, slotRefs, slotExpr);
if (children.get(i).findSlotRefByName(colName)) {
slotExpr.add(slotRefs);
setChild(i, slotRefs);
break;
}
}
}
private boolean findSlotRefByName(String colName) {
if (this instanceof SlotRef) {
SlotRef slot = (SlotRef) this;
if (slot.getColumnName() != null && slot.getColumnName().equals(colName)) {
return true;
}
} else if (this instanceof ColumnRefExpr) {
ColumnRefExpr slot = (ColumnRefExpr) this;
if (slot.getName() != null && slot.getName().equals(colName)) {
return true;
}
}
return false;
}
/**
* Looks up in the catalog the builtin for 'name' and 'argTypes'.
* Returns null if the function is not found.
*/
public Function getBuiltinFunction(String name, Type[] argTypes, Function.CompareMode mode)
throws AnalysisException {
FunctionName fnName = new FunctionName(name);
Function searchDesc = new Function(fnName, Arrays.asList(getActualArgTypes(argTypes)), Type.INVALID, false,
true);
Function f = Env.getCurrentEnv().getFunction(searchDesc, mode);
if (f != null && fnName.getFunction().equalsIgnoreCase("rand")) {
if (this.children.size() == 1 && !(this.children.get(0) instanceof LiteralExpr)) {
throw new AnalysisException("The param of rand function must be literal");
}
}
if (f != null) {
return f;
}
boolean isUnion = name.toLowerCase().endsWith(AGG_UNION_SUFFIX);
boolean isMerge = name.toLowerCase().endsWith(AGG_MERGE_SUFFIX);
boolean isState = name.toLowerCase().endsWith(AGG_STATE_SUFFIX);
if (isUnion || isMerge || isState) {
if (isUnion) {
name = name.substring(0, name.length() - AGG_UNION_SUFFIX.length());
}
if (isMerge) {
name = name.substring(0, name.length() - AGG_MERGE_SUFFIX.length());
}
if (isState) {
name = name.substring(0, name.length() - AGG_STATE_SUFFIX.length());
}
List<Type> argList = Arrays.asList(getActualArgTypes(argTypes));
List<Type> nestedArgList;
if (isState) {
nestedArgList = argList;
} else {
if (argList.size() != 1 || !argList.get(0).isAggStateType()) {
throw new AnalysisException("merge/union function must input one agg_state");
}
AggStateType aggState = (AggStateType) argList.get(0);
nestedArgList = aggState.getSubTypes();
}
searchDesc = new Function(new FunctionName(name), nestedArgList, Type.INVALID, false, true);
f = Env.getCurrentEnv().getFunction(searchDesc, mode);
if (f == null || !(f instanceof AggregateFunction)) {
return null;
}
if (isState) {
f = new ScalarFunction(new FunctionName(name + AGG_STATE_SUFFIX), Arrays.asList(f.getArgs()),
Expr.createAggStateType(name, nestedArgList,
nestedArgList.stream().map(e -> true).collect(Collectors.toList())),
f.hasVarArgs(), f.isUserVisible());
f.setNullableMode(NullableMode.ALWAYS_NOT_NULLABLE);
} else {
Function original = f;
f = f.clone();
f.setArgs(argList);
if (isUnion) {
f.setName(new FunctionName(name + AGG_UNION_SUFFIX));
f.setReturnType(argList.get(0));
f.setNullableMode(NullableMode.ALWAYS_NOT_NULLABLE);
}
if (isMerge) {
f.setName(new FunctionName(name + AGG_MERGE_SUFFIX));
f.setNullableMode(NullableMode.CUSTOM);
f.setNestedFunction(original);
}
}
f.setBinaryType(TFunctionBinaryType.AGG_STATE);
}
return f;
}
protected Function getTableFunction(String name, Type[] argTypes, Function.CompareMode mode) {
FunctionName fnName = new FunctionName(name);
Function searchDesc = new Function(fnName, Arrays.asList(argTypes), Type.INVALID, false);
Function f = Env.getCurrentEnv().getTableFunction(searchDesc, mode);
return f;
}
/**
* Pushes negation to the individual operands of a predicate
* tree rooted at 'root'.
*/
public static Expr pushNegationToOperands(Expr root) {
Preconditions.checkNotNull(root);
if (Expr.IS_NOT_PREDICATE.apply(root)) {
try {
// Make sure we call function 'negate' only on classes that support it,
// otherwise we may recurse infinitely.
root.getChild(0).getClass().getDeclaredMethod(NEGATE_FN);
return pushNegationToOperands(root.getChild(0).negate());
} catch (NoSuchMethodException e) {
// The 'negate' function is not implemented. Break the recursion.
return root;
}
}
if (root instanceof CompoundPredicate) {
Expr left = pushNegationToOperands(root.getChild(0));
Expr right = pushNegationToOperands(root.getChild(1));
CompoundPredicate compoundPredicate =
new CompoundPredicate(((CompoundPredicate) root).getOp(), left, right);
compoundPredicate.setPrintSqlInParens(root.getPrintSqlInParens());
return compoundPredicate;
}
return root;
}
/**
* Negates a boolean Expr.
*/
public Expr negate() {
Preconditions.checkState(type.equals(Type.BOOLEAN));
return new CompoundPredicate(CompoundPredicate.Operator.NOT, this, null);
}
/**
* Returns the subquery of an expr. Returns null if this expr does not contain
* a subquery.
*
* TODO: Support predicates with more that one subqueries when we implement
* the independent subquery evaluation.
*/
public Subquery getSubquery() {
if (!contains(Subquery.class)) {
return null;
}
List<Subquery> subqueries = Lists.newArrayList();
collect(Subquery.class, subqueries);
Preconditions.checkState(subqueries.size() == 1,
"only support one subquery in " + this.toSql());
return subqueries.get(0);
}
public boolean isCorrelatedPredicate(List<TupleId> tupleIdList) {
if (this instanceof SlotRef && !this.isBoundByTupleIds(tupleIdList)) {
return true;
}
for (Expr child : this.getChildren()) {
if (child.isCorrelatedPredicate(tupleIdList)) {
return true;
}
}
return false;
}
public void readFields(DataInput in) throws IOException {
throw new IOException("Not implemented serializable ");
}
enum ExprSerCode {
SLOT_REF(1),
NULL_LITERAL(2),
BOOL_LITERAL(3),
INT_LITERAL(4),
LARGE_INT_LITERAL(5),
FLOAT_LITERAL(6),
DECIMAL_LITERAL(7),
STRING_LITERAL(8),
DATE_LITERAL(9),
MAX_LITERAL(10),
BINARY_PREDICATE(11),
FUNCTION_CALL(12),
ARRAY_LITERAL(13),
CAST_EXPR(14),
JSON_LITERAL(15),
ARITHMETIC_EXPR(16),
STRUCT_LITERAL(17),
MAP_LITERAL(18);
private static Map<Integer, ExprSerCode> codeMap = Maps.newHashMap();
static {
for (ExprSerCode item : ExprSerCode.values()) {
codeMap.put(item.code, item);
}
}
private int code;
ExprSerCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static ExprSerCode fromCode(int code) {
return codeMap.get(code);
}
}
public static void writeTo(Expr expr, DataOutput output) throws IOException {
if (expr.supportSerializable()) {
Text.writeString(output, GsonUtils.GSON.toJson(expr));
} else {
throw new IOException("Unsupported writable expr " + expr.toSql());
}
}
/**
* The expr result may be null
* @param in
* @return
* @throws IOException
*/
public static Expr readIn(DataInput in) throws IOException {
if (Env.getCurrentEnvJournalVersion() >= FeMetaVersion.VERSION_133) {
return GsonUtils.GSON.fromJson(Text.readString(in), Expr.class);
} else {
int code = in.readInt();
ExprSerCode exprSerCode = ExprSerCode.fromCode(code);
if (exprSerCode == null) {
throw new IOException("Unknown code: " + code);
}
switch (exprSerCode) {
case SLOT_REF:
return SlotRef.read(in);
case NULL_LITERAL:
return NullLiteral.read(in);
case BOOL_LITERAL:
return BoolLiteral.read(in);
case INT_LITERAL:
return IntLiteral.read(in);
case DATE_LITERAL:
return DateLiteral.read(in);
case LARGE_INT_LITERAL:
return LargeIntLiteral.read(in);
case FLOAT_LITERAL:
return FloatLiteral.read(in);
case DECIMAL_LITERAL:
return DecimalLiteral.read(in);
case STRING_LITERAL:
return StringLiteral.read(in);
case JSON_LITERAL:
return JsonLiteral.read(in);
case MAX_LITERAL:
return MaxLiteral.read(in);
case BINARY_PREDICATE:
return BinaryPredicate.read(in);
case FUNCTION_CALL:
return FunctionCallExpr.read(in);
case ARRAY_LITERAL:
return ArrayLiteral.read(in);
case MAP_LITERAL:
return MapLiteral.read(in);
case STRUCT_LITERAL:
return StructLiteral.read(in);
case CAST_EXPR:
return CastExpr.read(in);
case ARITHMETIC_EXPR:
return ArithmeticExpr.read(in);
default:
throw new IOException("Unknown writable expr code: " + code);
}
}
}
// If this expr can serialize and deserialize.
// If one expr NOT implement Gson annotation, must override this function by return false
public boolean supportSerializable() {
for (Expr child : children) {
if (!child.supportSerializable()) {
return false;
}
}
return true;
}
protected void recursiveResetChildrenResult(boolean forPushDownPredicatesToView) throws AnalysisException {
for (int i = 0; i < children.size(); i++) {
final Expr child = children.get(i);
final Expr newChild = child.getResultValue(forPushDownPredicatesToView);
if (newChild != child) {
setChild(i, newChild);
}
}
}
/**
* For calculating expr.
* @return value returned can't be null, if this and it's children are't constant expr, return this.
* @throws AnalysisException
*/
public Expr getResultValue(boolean forPushDownPredicatesToView) throws AnalysisException {
recursiveResetChildrenResult(forPushDownPredicatesToView);
final Expr newExpr = ExpressionFunctions.INSTANCE.evalExpr(this);
return newExpr != null ? newExpr : this;
}
public String getStringValue() {
return "";
}
/**
* This method is used for constant fold of query in FE,
* for different serde dialect(hive, presto, doris).
*/
public String getStringValueForQuery(FormatOptions options) {
return getStringValue();
}
/**
* This method is to return the string value of this expr in a complex type for query
* It is only used for "getStringValueForQuery()"
* For most of the integer types, it is same as getStringValueForQuery().
* But for others like StringLiteral and DateLiteral, it should be wrapped with quotations.
* eg: 1,2,abc,[1,2,3],["abc","def"],{10:20},{"abc":20}
*/
protected String getStringValueInComplexTypeForQuery(FormatOptions options) {
return getStringValueForQuery(options);
}
/**
* This method is to return the string value of this expr for stream load.
* so there is a little different from "getStringValueForQuery()".
* eg, for NullLiteral, it should be "\N" for stream load, but "null" for FE constant
* for StructLiteral, the value should not contain sub column's name.
*/
public String getStringValueForStreamLoad(FormatOptions options) {
return getStringValueForQuery(options);
}
public final TExpr normalize(Normalizer normalizer) {
TExpr result = new TExpr();
treeToThriftHelper(result, (expr, texprNode) -> expr.normalize(texprNode, normalizer));
return result;
}
protected void normalize(TExprNode msg, Normalizer normalizer) {
this.toThrift(msg);
}
public static Expr getFirstBoundChild(Expr expr, List<TupleId> tids) {
for (Expr child : expr.getChildren()) {
if (child.isBoundByTupleIds(tids)) {
return child;
}
}
return null;
}
/**
* Returns true if expr contains specify function, otherwise false.
*/
public boolean isContainsFunction(String functionName) {
if (fn == null) {
return false;
}
if (fn.functionName().equalsIgnoreCase(functionName)) {
return true;
}
for (Expr child : children) {
if (child.isContainsFunction(functionName)) {
return true;
}
}
return false;
}
/**
* Returns true if expr contains specify className, otherwise false.
*/
public boolean isContainsClass(String className) {
if (this.getClass().getName().equalsIgnoreCase(className)) {
return true;
}
for (Expr child : children) {
if (child.isContainsClass(className)) {
return true;
}
}
return false;
}
protected boolean hasNullableChild() {
return hasNullableChild(children);
}
protected static boolean hasNullableChild(List<Expr> children) {
for (Expr expr : children) {
if (expr.isNullable()) {
return true;
}
}
return false;
}
public boolean hasAggregateSlot() {
for (Expr expr : children) {
if (expr.hasAggregateSlot()) {
return true;
}
}
return false;
}
public boolean hasAutoInc() {
for (Expr expr : children) {
if (expr.hasAutoInc()) {
return true;
}
}
return false;
}
/**
* For excute expr the result is nullable
* TODO: Now only SlotRef and LiteralExpr overwrite the method, each child of Expr should
* overwrite this method to plan correct
*/
public boolean isNullable() {
return isNullable(fn, children);
}
public static boolean isNullable(Function fn, List<Expr> children) {
if (fn == null) {
return true;
}
switch (fn.getNullableMode()) {
case DEPEND_ON_ARGUMENT:
return hasNullableChild(children);
case ALWAYS_NOT_NULLABLE:
return false;
case CUSTOM:
return customNullableAlgorithm(fn, children);
case ALWAYS_NULLABLE:
default:
return true;
}
}
private static boolean customNullableAlgorithm(Function fn, List<Expr> children) {
Preconditions.checkState(fn.getNullableMode() == Function.NullableMode.CUSTOM);
if (fn.functionName().endsWith(AGG_MERGE_SUFFIX)) {
AggStateType type = (AggStateType) fn.getArgs()[0];
return isNullable(fn.getNestedFunction(), getMockedExprs(type));
}
if (fn.functionName().equalsIgnoreCase("if")) {
Preconditions.checkState(children.size() == 3);
for (int i = 1; i < children.size(); i++) {
if (children.get(i).isNullable()) {
return true;
}
}
return false;
}
if (fn.functionName().equalsIgnoreCase("ifnull") || fn.functionName().equalsIgnoreCase("nvl")) {
Preconditions.checkState(children.size() == 2);
if (children.get(0).isNullable()) {
return children.get(1).isNullable();
}
return false;
}
if (fn.functionName().equalsIgnoreCase("coalesce")) {
for (Expr expr : children) {
if (!expr.isNullable()) {
return false;
}
}
return true;
}
if (fn.functionName().equalsIgnoreCase("concat_ws")) {
return children.get(0).isNullable();
}
if (fn.functionName().equalsIgnoreCase(Operator.MULTIPLY.getName())
&& fn.getReturnType().isDecimalV3()) {
return hasNullableChild(children);
}
if ((fn.functionName().equalsIgnoreCase(Operator.ADD.getName())
|| fn.functionName().equalsIgnoreCase(Operator.SUBTRACT.getName()))
&& fn.getReturnType().isDecimalV3()) {
return hasNullableChild(children);
}
if (fn.functionName().equalsIgnoreCase("group_concat")) {
int size = Math.min(fn.getNumArgs(), children.size());
for (int i = 0; i < size; ++i) {
if (children.get(i).isNullable()) {
return true;
}
}
return false;
}
if (fn.functionName().equalsIgnoreCase("array_sortby")) {
return children.get(0).isNullable();
}
if (fn.functionName().equalsIgnoreCase("array_map")) {
for (int i = 1; i < children.size(); ++i) {
if (children.get(i).isNullable()) {
return true;
}
}
return false;
}
if (fn.functionName().equalsIgnoreCase("array_contains") || fn.functionName().equalsIgnoreCase("array_position")
|| fn.functionName().equalsIgnoreCase("countequal")
|| fn.functionName().equalsIgnoreCase("map_contains_key")
|| fn.functionName().equalsIgnoreCase("map_contains_value")) {
return children.get(0).isNullable();
}
return true;
}
public static Boolean getResultIsNullable(String name, List<Type> typeList, List<Boolean> nullableList) {
if (name == null || typeList == null || nullableList == null) {
return false;
}
FunctionName fnName = new FunctionName(name);
Function searchDesc = new Function(fnName, typeList, Type.INVALID, false, true);
List<Expr> mockedExprs = getMockedExprs(typeList, nullableList);
Function f = Env.getCurrentEnv().getFunction(searchDesc, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
return isNullable(f, mockedExprs);
}
public static AggStateType createAggStateType(String name, List<Type> typeList, List<Boolean> nullableList) {
return new AggStateType(name, Expr.getResultIsNullable(name, typeList, nullableList), typeList, nullableList);
}
public static AggStateType createAggStateType(AggStateType type, List<Type> typeList, List<Boolean> nullableList) {
return new AggStateType(type.getFunctionName(),
Expr.getResultIsNullable(type.getFunctionName(), typeList, nullableList), typeList, nullableList);
}
public static List<Expr> getMockedExprs(List<Type> typeList, List<Boolean> nullableList) {
List<Expr> mockedExprs = Lists.newArrayList();
for (int i = 0; i < typeList.size(); i++) {
mockedExprs.add(new SlotRef(typeList.get(i), nullableList.get(i)));
}
return mockedExprs;
}
public static List<Expr> getMockedExprs(AggStateType type) {
return getMockedExprs(type.getSubTypes(), type.getSubTypeNullables());
}
public void materializeSrcExpr() {
if (this instanceof SlotRef) {
SlotRef thisRef = (SlotRef) this;
SlotDescriptor slotDesc = thisRef.getDesc();
slotDesc.setIsMaterialized(true);
slotDesc.getSourceExprs().forEach(Expr::materializeSrcExpr);
}
for (Expr child : children) {
child.materializeSrcExpr();
}
}
// This is only for transactional insert operation,
// to check it the given value in insert stmt is LiteralExpr.
// And if we write "1" to a boolean column, there will be a cast(1 as boolean) expr,
// which is also accepted.
public boolean isLiteralOrCastExpr() {
if (this instanceof CastExpr) {
return children.get(0) instanceof LiteralExpr;
} else {
return this instanceof LiteralExpr;
}
}
public boolean haveMvSlot(TupleId tid) {
for (Expr expr : getChildren()) {
if (expr.haveMvSlot(tid)) {
return true;
}
}
return false;
}
public boolean matchExprs(List<Expr> exprs, SelectStmt stmt, boolean ignoreAlias, TupleDescriptor tuple)
throws AnalysisException {
List<SlotRef> slots = new ArrayList<>();
collect(SlotRef.class, slots);
if (slots.size() == 0) {
return true;
}
String name = MaterializedIndexMeta.normalizeName(toSqlWithoutTbl());
for (Expr expr : exprs) {
if (CreateMaterializedViewStmt.isMVColumnNormal(name)
&& MaterializedIndexMeta.normalizeName(expr.toSqlWithoutTbl()).equals(CreateMaterializedViewStmt
.mvColumnBreaker(name))) {
return true;
}
if (MVExprEquivalent.aggregateArgumentEqual(this, expr)) {
return true;
}
}
if (getChildren().isEmpty()) {
// Match base index when expr not be rewritten
return !CreateMaterializedViewStmt.isMVColumn(name) && exprs.isEmpty();
}
for (Expr expr : getChildren()) {
if (!expr.matchExprs(exprs, stmt, ignoreAlias, tuple)) {
return false;
}
}
return true;
}
public boolean containsSubPredicate(Expr subExpr) throws AnalysisException {
if (toSqlWithoutTbl().equals(subExpr.toSqlWithoutTbl())) {
return true;
}
return false;
}
public Expr replaceSubPredicate(Expr subExpr) {
if (toSqlWithoutTbl().equals(subExpr.toSqlWithoutTbl())) {
return null;
}
return this;
}
protected Type getActualType(Type originType) {
if (originType == null) {
return null;
}
if (originType.isScalarType()) {
return getActualScalarType(originType);
} else if (originType.getPrimitiveType() == PrimitiveType.ARRAY) {
return getActualArrayType((ArrayType) originType);
} else if (originType.getPrimitiveType().isMapType()) {
return getActualMapType((MapType) originType);
} else {
return originType;
}
}
protected Type getActualScalarType(Type originType) {
if (originType.getPrimitiveType() == PrimitiveType.DECIMAL32) {
return Type.DECIMAL32;
} else if (originType.getPrimitiveType() == PrimitiveType.DECIMAL64) {
return Type.DECIMAL64;
} else if (originType.getPrimitiveType() == PrimitiveType.DECIMAL128) {
return Type.DECIMAL128;
} else if (originType.getPrimitiveType() == PrimitiveType.DECIMAL256) {
return Type.DECIMAL256;
} else if (originType.getPrimitiveType() == PrimitiveType.DATETIMEV2) {
return Type.DATETIMEV2;
} else if (originType.getPrimitiveType() == PrimitiveType.DATEV2) {
return Type.DATEV2;
} else if (originType.getPrimitiveType() == PrimitiveType.VARCHAR) {
return Type.VARCHAR;
} else if (originType.getPrimitiveType() == PrimitiveType.CHAR) {
return Type.CHAR;
} else if (originType.getPrimitiveType() == PrimitiveType.DECIMALV2) {
return Type.MAX_DECIMALV2_TYPE;
} else if (originType.getPrimitiveType() == PrimitiveType.TIMEV2) {
return Type.TIMEV2;
}
return originType;
}
protected Type[] getActualArgTypes(Type[] originType) {
return Arrays.stream(originType).map(this::getActualType).toArray(Type[]::new);
}
private MapType getActualMapType(MapType originMapType) {
return new MapType(originMapType.getKeyType(), originMapType.getValueType());
}
private ArrayType getActualArrayType(ArrayType originArrayType) {
return new ArrayType(getActualType(originArrayType.getItemType()));
}
public boolean refToCountStar() {
if (this instanceof SlotRef) {
SlotRef slotRef = (SlotRef) this;
SlotDescriptor desc = slotRef.getDesc();
List<Expr> exprs = desc.getSourceExprs();
return CollectionUtils.isNotEmpty(exprs) && exprs.stream().anyMatch(e -> {
if (e instanceof FunctionCallExpr) {
FunctionCallExpr funcExpr = (FunctionCallExpr) e;
Function f = funcExpr.fn;
// Return true if count function include non-literal expr child.
// In this case, agg output must be materialized whether outer query block required or not.
if (f.getFunctionName().getFunction().equals("count")) {
for (Expr expr : funcExpr.children) {
if (expr.isConstant() && !(expr instanceof LiteralExpr)) {
return true;
}
}
}
}
return false;
});
}
for (Expr expr : children) {
if (expr.refToCountStar()) {
return true;
}
}
return false;
}
public boolean haveFunction(String functionName) {
for (Expr expr : children) {
if (expr.haveFunction(functionName)) {
return true;
}
}
return false;
}
public void replaceSlot(TupleDescriptor tuple) {
for (Expr expr : getChildren()) {
expr.replaceSlot(tuple);
}
}
public boolean isNullLiteral() {
return this instanceof NullLiteral;
}
public boolean isZeroLiteral() {
return this instanceof LiteralExpr && ((LiteralExpr) this).isZero();
}
public void setNullableFromNereids(boolean nullable) {
nullableFromNereids = Optional.of(nullable);
}
public void clearNullableFromNereids() {
nullableFromNereids = Optional.empty();
}
public Set<SlotRef> getInputSlotRef() {
Set<SlotRef> slots = new HashSet<>();
if (this instanceof SlotRef) {
slots.add((SlotRef) this);
return slots;
} else {
for (Expr expr : children) {
slots.addAll(expr.getInputSlotRef());
}
}
return slots;
}
}