cherry pick from #36872 The new ExpressionAnalyzer can do both bind slot and bind function, so I remove SlotBinder and FunctionBinder.
This commit is contained in:
@ -32,6 +32,7 @@ import org.apache.doris.datasource.hive.HMSExternalTable;
|
||||
import org.apache.doris.datasource.iceberg.IcebergExternalDatabase;
|
||||
import org.apache.doris.datasource.iceberg.IcebergExternalTable;
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.analyzer.UnboundHiveTableSink;
|
||||
import org.apache.doris.nereids.analyzer.UnboundIcebergTableSink;
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
@ -42,7 +43,6 @@ import org.apache.doris.nereids.pattern.MatchingContext;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
|
||||
import org.apache.doris.nereids.rules.expression.rules.FunctionBinder;
|
||||
import org.apache.doris.nereids.trees.expressions.Alias;
|
||||
import org.apache.doris.nereids.trees.expressions.Cast;
|
||||
import org.apache.doris.nereids.trees.expressions.DefaultValueSlot;
|
||||
@ -53,7 +53,6 @@ import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.scalar.Substring;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.Literal;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
|
||||
import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.commands.info.DMLCommandType;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalHiveTableSink;
|
||||
@ -203,8 +202,8 @@ public class BindSink implements AnalysisRuleFactory {
|
||||
throw new AnalysisException(e.getMessage(), e.getCause());
|
||||
}
|
||||
|
||||
Map<String, NamedExpression> columnToOutput = getColumnToOutput(ctx, table, isPartialUpdate,
|
||||
boundSink, child);
|
||||
Map<String, NamedExpression> columnToOutput = getColumnToOutput(
|
||||
ctx, table, isPartialUpdate, boundSink, child);
|
||||
LogicalProject<?> fullOutputProject = getOutputProjectByCoercion(table.getFullSchema(), child, columnToOutput);
|
||||
return boundSink.withChildAndUpdateOutput(fullOutputProject);
|
||||
}
|
||||
@ -285,13 +284,11 @@ public class BindSink implements AnalysisRuleFactory {
|
||||
"mv column %s 's ref column cannot be null", column);
|
||||
Expression parsedExpression = expressionParser.parseExpression(
|
||||
column.getDefineExpr().toSqlWithoutTbl());
|
||||
Expression boundSlotExpression = SlotReplacer.INSTANCE
|
||||
.replace(parsedExpression, columnToOutput);
|
||||
// the boundSlotExpression is an expression whose slots are bound but function
|
||||
// may not be bound, we have to bind it again.
|
||||
// for example: to_bitmap.
|
||||
Expression boundExpression = FunctionBinder.INSTANCE.rewrite(
|
||||
boundSlotExpression, new ExpressionRewriteContext(ctx.cascadesContext));
|
||||
Expression boundExpression = new CustomExpressionAnalyzer(
|
||||
boundSink, ctx.cascadesContext, columnToOutput).analyze(parsedExpression);
|
||||
if (boundExpression instanceof Alias) {
|
||||
boundExpression = ((Alias) boundExpression).child();
|
||||
}
|
||||
@ -332,12 +329,15 @@ public class BindSink implements AnalysisRuleFactory {
|
||||
// update the value of the column to the current timestamp whenever there
|
||||
// is an update on the row
|
||||
if (column.hasOnUpdateDefaultValue()) {
|
||||
Expression defualtValueExpression = FunctionBinder.INSTANCE.rewrite(
|
||||
new NereidsParser().parseExpression(
|
||||
column.getOnUpdateDefaultValueExpr().toSqlWithoutTbl()),
|
||||
new ExpressionRewriteContext(ctx.cascadesContext));
|
||||
Expression unboundFunctionDefaultValue = new NereidsParser().parseExpression(
|
||||
column.getOnUpdateDefaultValueExpr().toSqlWithoutTbl()
|
||||
);
|
||||
Expression defualtValueExpression = ExpressionAnalyzer.analyzeFunction(
|
||||
boundSink, ctx.cascadesContext, unboundFunctionDefaultValue
|
||||
);
|
||||
columnToOutput.put(column.getName(),
|
||||
new Alias(defualtValueExpression, column.getName()));
|
||||
new Alias(defualtValueExpression, column.getName())
|
||||
);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@ -365,10 +365,10 @@ public class BindSink implements AnalysisRuleFactory {
|
||||
.checkedCastTo(DataType.fromCatalogType(column.getType())),
|
||||
column.getName()));
|
||||
} else {
|
||||
Expression defualtValueExpression = FunctionBinder.INSTANCE.rewrite(
|
||||
new NereidsParser().parseExpression(
|
||||
column.getDefaultValueExpr().toSqlWithoutTbl()),
|
||||
new ExpressionRewriteContext(ctx.cascadesContext));
|
||||
Expression unboundDefaultValue = new NereidsParser().parseExpression(
|
||||
column.getDefaultValueExpr().toSqlWithoutTbl());
|
||||
Expression defualtValueExpression = ExpressionAnalyzer.analyzeFunction(
|
||||
boundSink, ctx.cascadesContext, unboundDefaultValue);
|
||||
if (defualtValueExpression instanceof Alias) {
|
||||
defualtValueExpression = ((Alias) defualtValueExpression).child();
|
||||
}
|
||||
@ -568,19 +568,21 @@ public class BindSink implements AnalysisRuleFactory {
|
||||
&& !column.isMaterializedViewColumn();
|
||||
}
|
||||
|
||||
private static class SlotReplacer extends DefaultExpressionRewriter<Map<String, NamedExpression>> {
|
||||
public static final SlotReplacer INSTANCE = new SlotReplacer();
|
||||
private static class CustomExpressionAnalyzer extends ExpressionAnalyzer {
|
||||
private Map<String, NamedExpression> slotBinder;
|
||||
|
||||
public Expression replace(Expression e, Map<String, NamedExpression> replaceMap) {
|
||||
return e.accept(this, replaceMap);
|
||||
public CustomExpressionAnalyzer(
|
||||
Plan currentPlan, CascadesContext cascadesContext, Map<String, NamedExpression> slotBinder) {
|
||||
super(currentPlan, new Scope(ImmutableList.of()), cascadesContext, false, false);
|
||||
this.slotBinder = slotBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundSlot(UnboundSlot unboundSlot, Map<String, NamedExpression> replaceMap) {
|
||||
if (!replaceMap.containsKey(unboundSlot.getName())) {
|
||||
public Expression visitUnboundSlot(UnboundSlot unboundSlot, ExpressionRewriteContext context) {
|
||||
if (!slotBinder.containsKey(unboundSlot.getName())) {
|
||||
throw new AnalysisException("cannot find column from target table " + unboundSlot.getNameParts());
|
||||
}
|
||||
return replaceMap.get(unboundSlot.getName());
|
||||
return slotBinder.get(unboundSlot.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ import org.apache.doris.nereids.analyzer.UnboundStar;
|
||||
import org.apache.doris.nereids.analyzer.UnboundVariable;
|
||||
import org.apache.doris.nereids.analyzer.UnboundVariable.VariableType;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.rules.expression.AbstractExpressionRewriteRule;
|
||||
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
|
||||
import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
|
||||
import org.apache.doris.nereids.trees.expressions.Alias;
|
||||
@ -78,6 +79,7 @@ import org.apache.doris.nereids.trees.expressions.literal.Literal;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
|
||||
import org.apache.doris.nereids.trees.expressions.typecoercion.ImplicitCastInputTypes;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.nereids.types.ArrayType;
|
||||
import org.apache.doris.nereids.types.BigIntType;
|
||||
import org.apache.doris.nereids.types.BooleanType;
|
||||
@ -91,6 +93,7 @@ import org.apache.doris.qe.VariableMgr;
|
||||
import org.apache.doris.qe.VariableVarConverters;
|
||||
import org.apache.doris.qe.cache.CacheAnalyzer;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -102,9 +105,19 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** ExpressionAnalyzer */
|
||||
public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext> {
|
||||
@VisibleForTesting
|
||||
public static final AbstractExpressionRewriteRule FUNCTION_ANALYZER_RULE = new AbstractExpressionRewriteRule() {
|
||||
@Override
|
||||
public Expression rewrite(Expression expr, ExpressionRewriteContext ctx) {
|
||||
return new ExpressionAnalyzer(
|
||||
null, new Scope(ImmutableList.of()), null, false, false
|
||||
).analyze(expr, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
private final Plan currentPlan;
|
||||
/*
|
||||
@ -124,14 +137,30 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
private boolean hasNondeterministic;
|
||||
|
||||
/** ExpressionAnalyzer */
|
||||
public ExpressionAnalyzer(Plan currentPlan, Scope scope, CascadesContext cascadesContext,
|
||||
boolean enableExactMatch, boolean bindSlotInOuterScope) {
|
||||
public ExpressionAnalyzer(Plan currentPlan, Scope scope,
|
||||
@Nullable CascadesContext cascadesContext, boolean enableExactMatch, boolean bindSlotInOuterScope) {
|
||||
super(scope, cascadesContext);
|
||||
this.currentPlan = currentPlan;
|
||||
this.enableExactMatch = enableExactMatch;
|
||||
this.bindSlotInOuterScope = bindSlotInOuterScope;
|
||||
this.wantToParseSqlFromSqlCache = CacheAnalyzer.canUseSqlCache(
|
||||
cascadesContext.getConnectContext().getSessionVariable());
|
||||
this.wantToParseSqlFromSqlCache = cascadesContext != null
|
||||
&& CacheAnalyzer.canUseSqlCache(cascadesContext.getConnectContext().getSessionVariable());
|
||||
}
|
||||
|
||||
/** analyzeFunction */
|
||||
public static Expression analyzeFunction(
|
||||
@Nullable LogicalPlan plan, @Nullable CascadesContext cascadesContext, Expression expression) {
|
||||
ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plan, new Scope(ImmutableList.of()),
|
||||
cascadesContext, false, false);
|
||||
return analyzer.analyze(
|
||||
expression,
|
||||
cascadesContext == null ? null : new ExpressionRewriteContext(cascadesContext)
|
||||
);
|
||||
}
|
||||
|
||||
public Expression analyze(Expression expression) {
|
||||
CascadesContext cascadesContext = getCascadesContext();
|
||||
return analyze(expression, cascadesContext == null ? null : new ExpressionRewriteContext(cascadesContext));
|
||||
}
|
||||
|
||||
/** analyze */
|
||||
@ -258,10 +287,7 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
if (tableName.isEmpty()) {
|
||||
tableName = "table list";
|
||||
}
|
||||
throw new AnalysisException("Unknown column '"
|
||||
+ unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
|
||||
+ "' in '" + tableName + "' in "
|
||||
+ currentPlan.getType().toString().substring("LOGICAL_".length()) + " clause");
|
||||
couldNotFoundColumn(unboundSlot, tableName);
|
||||
}
|
||||
return unboundSlot;
|
||||
case 1:
|
||||
@ -300,6 +326,16 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
}
|
||||
}
|
||||
|
||||
protected void couldNotFoundColumn(UnboundSlot unboundSlot, String tableName) {
|
||||
String message = "Unknown column '"
|
||||
+ unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
|
||||
+ "' in '" + tableName;
|
||||
if (currentPlan != null) {
|
||||
message += "' in " + currentPlan.getType().toString().substring("LOGICAL_".length()) + " clause";
|
||||
}
|
||||
throw new AnalysisException(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundStar(UnboundStar unboundStar, ExpressionRewriteContext context) {
|
||||
List<String> qualifier = unboundStar.getQualifier();
|
||||
@ -356,15 +392,18 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
FunctionBuilder builder = functionRegistry.findFunctionBuilder(
|
||||
unboundFunction.getDbName(), functionName, arguments);
|
||||
Pair<? extends Expression, ? extends BoundFunction> buildResult = builder.build(functionName, arguments);
|
||||
StatementContext statementContext = context.cascadesContext.getStatementContext();
|
||||
if (buildResult.second instanceof Nondeterministic) {
|
||||
hasNondeterministic = true;
|
||||
}
|
||||
Optional<SqlCacheContext> sqlCacheContext = statementContext.getSqlCacheContext();
|
||||
if (builder instanceof AliasUdfBuilder
|
||||
|| buildResult.second instanceof JavaUdf || buildResult.second instanceof JavaUdaf) {
|
||||
if (sqlCacheContext.isPresent()) {
|
||||
sqlCacheContext.get().setCannotProcessExpression(true);
|
||||
Optional<SqlCacheContext> sqlCacheContext = Optional.empty();
|
||||
if (wantToParseSqlFromSqlCache) {
|
||||
StatementContext statementContext = context.cascadesContext.getStatementContext();
|
||||
if (buildResult.second instanceof Nondeterministic) {
|
||||
hasNondeterministic = true;
|
||||
}
|
||||
sqlCacheContext = statementContext.getSqlCacheContext();
|
||||
if (builder instanceof AliasUdfBuilder
|
||||
|| buildResult.second instanceof JavaUdf || buildResult.second instanceof JavaUdaf) {
|
||||
if (sqlCacheContext.isPresent()) {
|
||||
sqlCacheContext.get().setCannotProcessExpression(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (builder instanceof AliasUdfBuilder) {
|
||||
@ -376,9 +415,9 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
} else {
|
||||
Expression castFunction = TypeCoercionUtils.processBoundFunction((BoundFunction) buildResult.first);
|
||||
if (castFunction instanceof Count
|
||||
&& context != null
|
||||
&& context.cascadesContext.getOuterScope().isPresent()
|
||||
&& !context.cascadesContext.getOuterScope().get().getCorrelatedSlots()
|
||||
.isEmpty()) {
|
||||
&& !context.cascadesContext.getOuterScope().get().getCorrelatedSlots().isEmpty()) {
|
||||
// consider sql: SELECT * FROM t1 WHERE t1.a <= (SELECT COUNT(t2.a) FROM t2 WHERE (t1.b = t2.b));
|
||||
// when unnest correlated subquery, we create a left join node.
|
||||
// outer query is left table and subquery is right one
|
||||
@ -488,6 +527,9 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
|
||||
@Override
|
||||
public Expression visitPlaceholder(Placeholder placeholder, ExpressionRewriteContext context) {
|
||||
if (context == null) {
|
||||
return super.visitPlaceholder(placeholder, context);
|
||||
}
|
||||
Expression realExpr = context.cascadesContext.getStatementContext()
|
||||
.getIdToPlaceholderRealExpr().get(placeholder.getPlaceholderId());
|
||||
return visit(realExpr, context);
|
||||
@ -714,17 +756,6 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBoundLambda(Expression lambdaFunction, List<String> argumentNames) {
|
||||
lambdaFunction.foreachUp(e -> {
|
||||
if (e instanceof UnboundSlot) {
|
||||
UnboundSlot unboundSlot = (UnboundSlot) e;
|
||||
throw new AnalysisException("Unknown lambda slot '"
|
||||
+ unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
|
||||
+ " in lambda arguments" + argumentNames);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private UnboundFunction bindHighOrderFunction(UnboundFunction unboundFunction, ExpressionRewriteContext context) {
|
||||
int childrenSize = unboundFunction.children().size();
|
||||
List<Expression> subChildren = new ArrayList<>();
|
||||
@ -737,16 +768,20 @@ public class ExpressionAnalyzer extends SubExprAnalyzer<ExpressionRewriteContext
|
||||
Expression lambdaFunction = lambda.getLambdaFunction();
|
||||
List<ArrayItemReference> arrayItemReferences = lambda.makeArguments(subChildren);
|
||||
|
||||
// 1.bindSlot
|
||||
List<Slot> boundedSlots = arrayItemReferences.stream()
|
||||
.map(ArrayItemReference::toSlot)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
lambdaFunction = new SlotBinder(new Scope(boundedSlots), context.cascadesContext,
|
||||
true, false).bind(lambdaFunction);
|
||||
checkBoundLambda(lambdaFunction, lambda.getLambdaArgumentNames());
|
||||
|
||||
// 2.bindFunction
|
||||
lambdaFunction = lambdaFunction.accept(this, context);
|
||||
ExpressionAnalyzer lambdaAnalyzer = new ExpressionAnalyzer(currentPlan, new Scope(boundedSlots),
|
||||
context == null ? null : context.cascadesContext, true, false) {
|
||||
@Override
|
||||
protected void couldNotFoundColumn(UnboundSlot unboundSlot, String tableName) {
|
||||
throw new AnalysisException("Unknown lambda slot '"
|
||||
+ unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
|
||||
+ " in lambda arguments" + lambda.getLambdaArgumentNames());
|
||||
}
|
||||
};
|
||||
lambdaFunction = lambdaAnalyzer.analyze(lambdaFunction, context);
|
||||
|
||||
Lambda lambdaClosure = lambda.withLambdaFunctionArguments(lambdaFunction, arrayItemReferences);
|
||||
|
||||
|
||||
@ -1,320 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.analysis;
|
||||
|
||||
import org.apache.doris.analysis.SetType;
|
||||
import org.apache.doris.common.DdlException;
|
||||
import org.apache.doris.common.util.Util;
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.analyzer.UnboundAlias;
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.analyzer.UnboundStar;
|
||||
import org.apache.doris.nereids.analyzer.UnboundVariable;
|
||||
import org.apache.doris.nereids.analyzer.UnboundVariable.VariableType;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.trees.expressions.Alias;
|
||||
import org.apache.doris.nereids.trees.expressions.BoundStar;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.SlotReference;
|
||||
import org.apache.doris.nereids.trees.expressions.Variable;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.Literal;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
import org.apache.doris.qe.GlobalVariable;
|
||||
import org.apache.doris.qe.SessionVariable;
|
||||
import org.apache.doris.qe.VariableMgr;
|
||||
import org.apache.doris.qe.VariableVarConverters;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* SlotBinder is used to bind slot
|
||||
*/
|
||||
public class SlotBinder extends SubExprAnalyzer<CascadesContext> {
|
||||
/*
|
||||
bounded={table.a, a}
|
||||
unbound=a
|
||||
if enableExactMatch, 'a' is bound to bounded 'a',
|
||||
if not enableExactMatch, 'a' is ambiguous
|
||||
in order to be compatible to original planner,
|
||||
exact match mode is not enabled for having clause
|
||||
but enabled for order by clause
|
||||
TODO after remove original planner, always enable exact match mode.
|
||||
*/
|
||||
private final boolean enableExactMatch;
|
||||
private final boolean bindSlotInOuterScope;
|
||||
|
||||
public SlotBinder(Scope scope, CascadesContext cascadesContext) {
|
||||
this(scope, cascadesContext, true, true);
|
||||
}
|
||||
|
||||
public SlotBinder(Scope scope, CascadesContext cascadesContext,
|
||||
boolean enableExactMatch, boolean bindSlotInOuterScope) {
|
||||
super(scope, cascadesContext);
|
||||
this.enableExactMatch = enableExactMatch;
|
||||
this.bindSlotInOuterScope = bindSlotInOuterScope;
|
||||
}
|
||||
|
||||
public Expression bind(Expression expression) {
|
||||
return expression.accept(this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundVariable(UnboundVariable unboundVariable, CascadesContext context) {
|
||||
String name = unboundVariable.getName();
|
||||
SessionVariable sessionVariable = ConnectContext.get().getSessionVariable();
|
||||
Literal literal = null;
|
||||
if (unboundVariable.getType() == VariableType.DEFAULT) {
|
||||
literal = VariableMgr.getLiteral(sessionVariable, name, SetType.DEFAULT);
|
||||
} else if (unboundVariable.getType() == VariableType.SESSION) {
|
||||
literal = VariableMgr.getLiteral(sessionVariable, name, SetType.SESSION);
|
||||
} else if (unboundVariable.getType() == VariableType.GLOBAL) {
|
||||
literal = VariableMgr.getLiteral(sessionVariable, name, SetType.GLOBAL);
|
||||
} else if (unboundVariable.getType() == VariableType.USER) {
|
||||
literal = ConnectContext.get().getLiteralForUserVar(name);
|
||||
}
|
||||
if (literal == null) {
|
||||
throw new AnalysisException("Unsupported system variable: " + unboundVariable.getName());
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(name) && VariableVarConverters.hasConverter(name)) {
|
||||
try {
|
||||
Preconditions.checkArgument(literal instanceof IntegerLikeLiteral);
|
||||
IntegerLikeLiteral integerLikeLiteral = (IntegerLikeLiteral) literal;
|
||||
literal = new StringLiteral(VariableVarConverters.decode(name, integerLikeLiteral.getLongValue()));
|
||||
} catch (DdlException e) {
|
||||
throw new AnalysisException(e.getMessage());
|
||||
}
|
||||
}
|
||||
return new Variable(unboundVariable.getName(), unboundVariable.getType(), literal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundAlias(UnboundAlias unboundAlias, CascadesContext context) {
|
||||
Expression child = unboundAlias.child().accept(this, context);
|
||||
if (unboundAlias.getAlias().isPresent()) {
|
||||
return new Alias(child, unboundAlias.getAlias().get());
|
||||
} else if (child instanceof NamedExpression) {
|
||||
return new Alias(child, ((NamedExpression) child).getName());
|
||||
} else {
|
||||
return new Alias(child);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slot visitUnboundSlot(UnboundSlot unboundSlot, CascadesContext context) {
|
||||
Optional<List<Slot>> boundedOpt = Optional.of(bindSlot(unboundSlot, getScope().getSlots()));
|
||||
boolean foundInThisScope = !boundedOpt.get().isEmpty();
|
||||
// Currently only looking for symbols on the previous level.
|
||||
if (bindSlotInOuterScope && !foundInThisScope && getScope().getOuterScope().isPresent()) {
|
||||
boundedOpt = Optional.of(bindSlot(unboundSlot,
|
||||
getScope()
|
||||
.getOuterScope()
|
||||
.get()
|
||||
.getSlots()));
|
||||
}
|
||||
List<Slot> bounded = boundedOpt.get();
|
||||
switch (bounded.size()) {
|
||||
case 0:
|
||||
// just return, give a chance to bind on another slot.
|
||||
// if unbound finally, check will throw exception
|
||||
return unboundSlot;
|
||||
case 1:
|
||||
if (!foundInThisScope
|
||||
&& !getScope().getOuterScope().get().getCorrelatedSlots().contains(bounded.get(0))) {
|
||||
getScope().getOuterScope().get().getCorrelatedSlots().add(bounded.get(0));
|
||||
}
|
||||
return bounded.get(0);
|
||||
default:
|
||||
if (enableExactMatch) {
|
||||
// select t1.k k, t2.k
|
||||
// from t1 join t2 order by k
|
||||
//
|
||||
// 't1.k k' is denoted by alias_k, its full name is 'k'
|
||||
// 'order by k' is denoted as order_k, it full name is 'k'
|
||||
// 't2.k' in select list, its full name is 't2.k'
|
||||
//
|
||||
// order_k can be bound on alias_k and t2.k
|
||||
// alias_k is exactly matched, since its full name is exactly match full name of order_k
|
||||
// t2.k is not exactly matched, since t2.k's full name is larger than order_k
|
||||
List<Slot> exactMatch = bounded.stream()
|
||||
.filter(bound -> unboundSlot.getNameParts().size() == bound.getQualifier().size() + 1)
|
||||
.collect(Collectors.toList());
|
||||
if (exactMatch.size() == 1) {
|
||||
return exactMatch.get(0);
|
||||
}
|
||||
}
|
||||
throw new AnalysisException(String.format("%s is ambiguous: %s.",
|
||||
unboundSlot.toSql(),
|
||||
bounded.stream()
|
||||
.map(Slot::toString)
|
||||
.collect(Collectors.joining(", "))));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundStar(UnboundStar unboundStar, CascadesContext context) {
|
||||
List<String> qualifier = unboundStar.getQualifier();
|
||||
boolean showHidden = Util.showHiddenColumns();
|
||||
List<Slot> slots = getScope().getSlots()
|
||||
.stream()
|
||||
.filter(slot -> !(slot instanceof SlotReference)
|
||||
|| (((SlotReference) slot).isVisible()) || showHidden)
|
||||
.collect(Collectors.toList());
|
||||
switch (qualifier.size()) {
|
||||
case 0: // select *
|
||||
return new BoundStar(slots);
|
||||
case 1: // select table.*
|
||||
case 2: // select db.table.*
|
||||
case 3: // select catalog.db.table.*
|
||||
return bindQualifiedStar(qualifier, slots);
|
||||
default:
|
||||
throw new AnalysisException("Not supported qualifier: "
|
||||
+ StringUtils.join(qualifier, "."));
|
||||
}
|
||||
}
|
||||
|
||||
private BoundStar bindQualifiedStar(List<String> qualifierStar, List<Slot> boundSlots) {
|
||||
// FIXME: compatible with previous behavior:
|
||||
// https://github.com/apache/doris/pull/10415/files/3fe9cb0c3f805ab3a9678033b281b16ad93ec60a#r910239452
|
||||
List<Slot> slots = boundSlots.stream().filter(boundSlot -> {
|
||||
switch (qualifierStar.size()) {
|
||||
// table.*
|
||||
case 1:
|
||||
List<String> boundSlotQualifier = boundSlot.getQualifier();
|
||||
switch (boundSlotQualifier.size()) {
|
||||
// bound slot is `column` and no qualified
|
||||
case 0:
|
||||
return false;
|
||||
case 1: // bound slot is `table`.`column`
|
||||
return qualifierStar.get(0).equalsIgnoreCase(boundSlotQualifier.get(0));
|
||||
case 2:// bound slot is `db`.`table`.`column`
|
||||
return qualifierStar.get(0).equalsIgnoreCase(boundSlotQualifier.get(1));
|
||||
case 3:// bound slot is `catalog`.`db`.`table`.`column`
|
||||
return qualifierStar.get(0).equalsIgnoreCase(boundSlotQualifier.get(2));
|
||||
default:
|
||||
throw new AnalysisException("Not supported qualifier: "
|
||||
+ StringUtils.join(qualifierStar, "."));
|
||||
}
|
||||
case 2: // db.table.*
|
||||
boundSlotQualifier = boundSlot.getQualifier();
|
||||
switch (boundSlotQualifier.size()) {
|
||||
// bound slot is `column` and no qualified
|
||||
case 0:
|
||||
case 1: // bound slot is `table`.`column`
|
||||
return false;
|
||||
case 2:// bound slot is `db`.`table`.`column`
|
||||
return compareDbName(qualifierStar.get(0), boundSlotQualifier.get(0))
|
||||
&& qualifierStar.get(1).equalsIgnoreCase(boundSlotQualifier.get(1));
|
||||
case 3:// bound slot is `catalog`.`db`.`table`.`column`
|
||||
return compareDbName(qualifierStar.get(0), boundSlotQualifier.get(1))
|
||||
&& qualifierStar.get(1).equalsIgnoreCase(boundSlotQualifier.get(2));
|
||||
default:
|
||||
throw new AnalysisException("Not supported qualifier: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
case 3: // catalog.db.table.*
|
||||
boundSlotQualifier = boundSlot.getQualifier();
|
||||
switch (boundSlotQualifier.size()) {
|
||||
// bound slot is `column` and no qualified
|
||||
case 0:
|
||||
case 1: // bound slot is `table`.`column`
|
||||
case 2: // bound slot is `db`.`table`.`column`
|
||||
return false;
|
||||
case 3:// bound slot is `catalog`.`db`.`table`.`column`
|
||||
return qualifierStar.get(0).equalsIgnoreCase(boundSlotQualifier.get(0))
|
||||
&& compareDbName(qualifierStar.get(1), boundSlotQualifier.get(1))
|
||||
&& qualifierStar.get(2).equalsIgnoreCase(boundSlotQualifier.get(2));
|
||||
default:
|
||||
throw new AnalysisException("Not supported qualifier: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
default:
|
||||
throw new AnalysisException("Not supported name: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
if (slots.isEmpty()) {
|
||||
throw new AnalysisException("unknown qualifier: " + StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
return new BoundStar(slots);
|
||||
}
|
||||
|
||||
private List<Slot> bindSlot(UnboundSlot unboundSlot, List<Slot> boundSlots) {
|
||||
return boundSlots.stream().distinct().filter(boundSlot -> {
|
||||
List<String> nameParts = unboundSlot.getNameParts();
|
||||
int qualifierSize = boundSlot.getQualifier().size();
|
||||
int namePartsSize = nameParts.size();
|
||||
if (namePartsSize > qualifierSize + 1) {
|
||||
return false;
|
||||
}
|
||||
if (namePartsSize == 1) {
|
||||
return nameParts.get(0).equalsIgnoreCase(boundSlot.getName());
|
||||
}
|
||||
if (namePartsSize == 2) {
|
||||
String qualifierTableName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
return sameTableName(qualifierTableName, nameParts.get(0))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(1));
|
||||
}
|
||||
if (nameParts.size() == 3) {
|
||||
String qualifierTableName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
String qualifierDbName = boundSlot.getQualifier().get(qualifierSize - 2);
|
||||
return compareDbName(nameParts.get(0), qualifierDbName)
|
||||
&& sameTableName(qualifierTableName, nameParts.get(1))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(2));
|
||||
}
|
||||
// catalog.db.table.column
|
||||
if (nameParts.size() == 4) {
|
||||
String qualifierTableName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
String qualifierDbName = boundSlot.getQualifier().get(qualifierSize - 2);
|
||||
String qualifierCatalogName = boundSlot.getQualifier().get(qualifierSize - 3);
|
||||
return qualifierCatalogName.equalsIgnoreCase(nameParts.get(0))
|
||||
&& compareDbName(nameParts.get(1), qualifierDbName)
|
||||
&& sameTableName(qualifierTableName, nameParts.get(2))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(3));
|
||||
}
|
||||
//TODO: handle name parts more than three.
|
||||
throw new AnalysisException("Not supported name: "
|
||||
+ StringUtils.join(nameParts, "."));
|
||||
})
|
||||
.map(s -> s.withName(unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static boolean compareDbName(String boundedDbName, String unBoundDbName) {
|
||||
return unBoundDbName.equalsIgnoreCase(boundedDbName);
|
||||
}
|
||||
|
||||
public static boolean sameTableName(String boundSlot, String unboundSlot) {
|
||||
if (GlobalVariable.lowerCaseTableNames != 1) {
|
||||
return boundSlot.equals(unboundSlot);
|
||||
} else {
|
||||
return boundSlot.equalsIgnoreCase(unboundSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,6 +173,9 @@ class SubExprAnalyzer<T> extends DefaultExpressionRewriter<T> {
|
||||
}
|
||||
|
||||
private AnalyzedResult analyzeSubquery(SubqueryExpr expr) {
|
||||
if (cascadesContext == null) {
|
||||
throw new IllegalStateException("Missing CascadesContext");
|
||||
}
|
||||
CascadesContext subqueryContext = CascadesContext.newContextWithCteContext(
|
||||
cascadesContext, expr.getQueryPlan(), cascadesContext.getCteContext());
|
||||
Scope subqueryScope = genScopeWithSubquery(expr);
|
||||
|
||||
@ -1,365 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.expression.rules;
|
||||
|
||||
import org.apache.doris.analysis.ArithmeticExpr.Operator;
|
||||
import org.apache.doris.catalog.Env;
|
||||
import org.apache.doris.catalog.FunctionRegistry;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.analyzer.UnboundFunction;
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.rules.analysis.ArithmeticFunctionBinder;
|
||||
import org.apache.doris.nereids.rules.analysis.SlotBinder;
|
||||
import org.apache.doris.nereids.rules.expression.AbstractExpressionRewriteRule;
|
||||
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
|
||||
import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
|
||||
import org.apache.doris.nereids.trees.expressions.BinaryArithmetic;
|
||||
import org.apache.doris.nereids.trees.expressions.BitNot;
|
||||
import org.apache.doris.nereids.trees.expressions.CaseWhen;
|
||||
import org.apache.doris.nereids.trees.expressions.Cast;
|
||||
import org.apache.doris.nereids.trees.expressions.ComparisonPredicate;
|
||||
import org.apache.doris.nereids.trees.expressions.CompoundPredicate;
|
||||
import org.apache.doris.nereids.trees.expressions.Divide;
|
||||
import org.apache.doris.nereids.trees.expressions.EqualTo;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.InPredicate;
|
||||
import org.apache.doris.nereids.trees.expressions.InSubquery;
|
||||
import org.apache.doris.nereids.trees.expressions.IntegralDivide;
|
||||
import org.apache.doris.nereids.trees.expressions.ListQuery;
|
||||
import org.apache.doris.nereids.trees.expressions.Match;
|
||||
import org.apache.doris.nereids.trees.expressions.Not;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.TimestampArithmetic;
|
||||
import org.apache.doris.nereids.trees.expressions.WhenClause;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.FunctionBuilder;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.scalar.Nvl;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.udf.AliasUdfBuilder;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
|
||||
import org.apache.doris.nereids.trees.expressions.typecoercion.ImplicitCastInputTypes;
|
||||
import org.apache.doris.nereids.types.ArrayType;
|
||||
import org.apache.doris.nereids.types.BigIntType;
|
||||
import org.apache.doris.nereids.types.BooleanType;
|
||||
import org.apache.doris.nereids.types.DataType;
|
||||
import org.apache.doris.nereids.util.TypeCoercionUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* function binder
|
||||
*/
|
||||
public class FunctionBinder extends AbstractExpressionRewriteRule {
|
||||
|
||||
public static final FunctionBinder INSTANCE = new FunctionBinder();
|
||||
|
||||
@Override
|
||||
public Expression visit(Expression expr, ExpressionRewriteContext context) {
|
||||
expr = super.visit(expr, context);
|
||||
expr.checkLegalityBeforeTypeCoercion();
|
||||
// this cannot be removed, because some function already construct in parser.
|
||||
if (expr instanceof ImplicitCastInputTypes) {
|
||||
List<DataType> expectedInputTypes = ((ImplicitCastInputTypes) expr).expectedInputTypes();
|
||||
if (!expectedInputTypes.isEmpty()) {
|
||||
return TypeCoercionUtils.implicitCastInputTypes(expr, expectedInputTypes);
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/* ********************************************************************************************
|
||||
* bind function
|
||||
* ******************************************************************************************** */
|
||||
private void checkBoundLambda(Expression lambdaFunction, List<String> argumentNames) {
|
||||
lambdaFunction.foreachUp(e -> {
|
||||
if (e instanceof UnboundSlot) {
|
||||
UnboundSlot unboundSlot = (UnboundSlot) e;
|
||||
throw new AnalysisException("Unknown lambda slot '"
|
||||
+ unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
|
||||
+ " in lambda arguments" + argumentNames);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private UnboundFunction bindHighOrderFunction(UnboundFunction unboundFunction, ExpressionRewriteContext context) {
|
||||
int childrenSize = unboundFunction.children().size();
|
||||
List<Expression> subChildren = new ArrayList<>();
|
||||
for (int i = 1; i < childrenSize; i++) {
|
||||
subChildren.add(unboundFunction.child(i).accept(this, context));
|
||||
}
|
||||
|
||||
// bindLambdaFunction
|
||||
Lambda lambda = (Lambda) unboundFunction.children().get(0);
|
||||
Expression lambdaFunction = lambda.getLambdaFunction();
|
||||
List<ArrayItemReference> arrayItemReferences = lambda.makeArguments(subChildren);
|
||||
|
||||
// 1.bindSlot
|
||||
List<Slot> boundedSlots = arrayItemReferences.stream()
|
||||
.map(ArrayItemReference::toSlot)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
lambdaFunction = new SlotBinder(new Scope(boundedSlots), context.cascadesContext,
|
||||
true, false).bind(lambdaFunction);
|
||||
checkBoundLambda(lambdaFunction, lambda.getLambdaArgumentNames());
|
||||
|
||||
// 2.bindFunction
|
||||
lambdaFunction = lambdaFunction.accept(this, context);
|
||||
|
||||
Lambda lambdaClosure = lambda.withLambdaFunctionArguments(lambdaFunction, arrayItemReferences);
|
||||
|
||||
// We don't add the ArrayExpression in high order function at all
|
||||
return unboundFunction.withChildren(ImmutableList.<Expression>builder()
|
||||
.add(lambdaClosure)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundFunction(UnboundFunction unboundFunction, ExpressionRewriteContext context) {
|
||||
if (unboundFunction.isHighOrder()) {
|
||||
unboundFunction = bindHighOrderFunction(unboundFunction, context);
|
||||
} else {
|
||||
unboundFunction = unboundFunction.withChildren(unboundFunction.children().stream()
|
||||
.map(e -> e.accept(this, context)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// bind function
|
||||
FunctionRegistry functionRegistry = Env.getCurrentEnv().getFunctionRegistry();
|
||||
List<Object> arguments = unboundFunction.isDistinct()
|
||||
? ImmutableList.builder()
|
||||
.add(unboundFunction.isDistinct())
|
||||
.addAll(unboundFunction.getArguments())
|
||||
.build()
|
||||
: (List) unboundFunction.getArguments();
|
||||
|
||||
if (StringUtils.isEmpty(unboundFunction.getDbName())) {
|
||||
// we will change arithmetic function like add(), subtract(), bitnot()
|
||||
// to the corresponding objects rather than BoundFunction.
|
||||
ArithmeticFunctionBinder functionBinder = new ArithmeticFunctionBinder();
|
||||
if (functionBinder.isBinaryArithmetic(unboundFunction.getName())) {
|
||||
return functionBinder.bindBinaryArithmetic(unboundFunction.getName(), unboundFunction.children())
|
||||
.accept(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
String functionName = unboundFunction.getName();
|
||||
FunctionBuilder builder = functionRegistry.findFunctionBuilder(
|
||||
unboundFunction.getDbName(), functionName, arguments);
|
||||
if (builder instanceof AliasUdfBuilder) {
|
||||
// we do type coercion in build function in alias function, so it's ok to return directly.
|
||||
return builder.build(functionName, arguments).first;
|
||||
} else {
|
||||
Expression boundFunction = TypeCoercionUtils
|
||||
.processBoundFunction((BoundFunction) builder.build(functionName, arguments).first);
|
||||
if (boundFunction instanceof Count
|
||||
&& context.cascadesContext.getOuterScope().isPresent()
|
||||
&& !context.cascadesContext.getOuterScope().get().getCorrelatedSlots()
|
||||
.isEmpty()) {
|
||||
// consider sql: SELECT * FROM t1 WHERE t1.a <= (SELECT COUNT(t2.a) FROM t2 WHERE (t1.b = t2.b));
|
||||
// when unnest correlated subquery, we create a left join node.
|
||||
// outer query is left table and subquery is right one
|
||||
// if there is no match, the row from right table is filled with nulls
|
||||
// but COUNT function is always not nullable.
|
||||
// so wrap COUNT with Nvl to ensure it's result is 0 instead of null to get the correct result
|
||||
boundFunction = new Nvl(boundFunction, new BigIntLiteral(0));
|
||||
}
|
||||
return boundFunction;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitBoundFunction(BoundFunction boundFunction, ExpressionRewriteContext context) {
|
||||
boundFunction = (BoundFunction) super.visitBoundFunction(boundFunction, context);
|
||||
return TypeCoercionUtils.processBoundFunction(boundFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the method for calculating the time.
|
||||
* e.g. YEARS_ADD、YEARS_SUB、DAYS_ADD 、DAYS_SUB
|
||||
*/
|
||||
@Override
|
||||
public Expression visitTimestampArithmetic(TimestampArithmetic arithmetic, ExpressionRewriteContext context) {
|
||||
Expression left = arithmetic.left().accept(this, context);
|
||||
Expression right = arithmetic.right().accept(this, context);
|
||||
|
||||
arithmetic = (TimestampArithmetic) arithmetic.withChildren(left, right);
|
||||
// bind function
|
||||
String funcOpName;
|
||||
if (arithmetic.getFuncName() == null) {
|
||||
// e.g. YEARS_ADD, MONTHS_SUB
|
||||
funcOpName = String.format("%sS_%s", arithmetic.getTimeUnit(),
|
||||
(arithmetic.getOp() == Operator.ADD) ? "ADD" : "SUB");
|
||||
} else {
|
||||
funcOpName = arithmetic.getFuncName();
|
||||
}
|
||||
arithmetic = (TimestampArithmetic) arithmetic.withFuncName(funcOpName.toLowerCase(Locale.ROOT));
|
||||
|
||||
// type coercion
|
||||
return TypeCoercionUtils.processTimestampArithmetic(arithmetic);
|
||||
}
|
||||
|
||||
/* ********************************************************************************************
|
||||
* type coercion
|
||||
* ******************************************************************************************** */
|
||||
|
||||
@Override
|
||||
public Expression visitBitNot(BitNot bitNot, ExpressionRewriteContext context) {
|
||||
Expression child = bitNot.child().accept(this, context);
|
||||
// type coercion
|
||||
if (!(child.getDataType().isIntegralType() || child.getDataType().isBooleanType())) {
|
||||
child = new Cast(child, BigIntType.INSTANCE);
|
||||
}
|
||||
return bitNot.withChildren(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitDivide(Divide divide, ExpressionRewriteContext context) {
|
||||
Expression left = divide.left().accept(this, context);
|
||||
Expression right = divide.right().accept(this, context);
|
||||
divide = (Divide) divide.withChildren(left, right);
|
||||
// type coercion
|
||||
return TypeCoercionUtils.processDivide(divide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitIntegralDivide(IntegralDivide integralDivide, ExpressionRewriteContext context) {
|
||||
Expression left = integralDivide.left().accept(this, context);
|
||||
Expression right = integralDivide.right().accept(this, context);
|
||||
integralDivide = (IntegralDivide) integralDivide.withChildren(left, right);
|
||||
// type coercion
|
||||
return TypeCoercionUtils.processIntegralDivide(integralDivide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitBinaryArithmetic(BinaryArithmetic binaryArithmetic, ExpressionRewriteContext context) {
|
||||
Expression left = binaryArithmetic.left().accept(this, context);
|
||||
Expression right = binaryArithmetic.right().accept(this, context);
|
||||
binaryArithmetic = (BinaryArithmetic) binaryArithmetic.withChildren(left, right);
|
||||
return TypeCoercionUtils.processBinaryArithmetic(binaryArithmetic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitCompoundPredicate(CompoundPredicate compoundPredicate, ExpressionRewriteContext context) {
|
||||
Expression left = compoundPredicate.left().accept(this, context);
|
||||
Expression right = compoundPredicate.right().accept(this, context);
|
||||
CompoundPredicate ret = (CompoundPredicate) compoundPredicate.withChildren(left, right);
|
||||
return TypeCoercionUtils.processCompoundPredicate(ret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitNot(Not not, ExpressionRewriteContext context) {
|
||||
Expression child = not.child().accept(this, context);
|
||||
child = TypeCoercionUtils.castIfNotSameType(child, BooleanType.INSTANCE);
|
||||
return not.withChildren(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitComparisonPredicate(ComparisonPredicate cp, ExpressionRewriteContext context) {
|
||||
Expression left = cp.left().accept(this, context);
|
||||
Expression right = cp.right().accept(this, context);
|
||||
cp = (ComparisonPredicate) cp.withChildren(left, right);
|
||||
return TypeCoercionUtils.processComparisonPredicate(cp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitCaseWhen(CaseWhen caseWhen, ExpressionRewriteContext context) {
|
||||
List<Expression> rewrittenChildren = caseWhen.children().stream()
|
||||
.map(e -> e.accept(this, context)).collect(Collectors.toList());
|
||||
CaseWhen newCaseWhen = caseWhen.withChildren(rewrittenChildren);
|
||||
newCaseWhen.checkLegalityBeforeTypeCoercion();
|
||||
return TypeCoercionUtils.processCaseWhen(newCaseWhen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitWhenClause(WhenClause whenClause, ExpressionRewriteContext context) {
|
||||
return whenClause.withChildren(TypeCoercionUtils.castIfNotSameType(
|
||||
whenClause.getOperand().accept(this, context), BooleanType.INSTANCE),
|
||||
whenClause.getResult().accept(this, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitInPredicate(InPredicate inPredicate, ExpressionRewriteContext context) {
|
||||
List<Expression> rewrittenChildren = inPredicate.children().stream()
|
||||
.map(e -> e.accept(this, context)).collect(Collectors.toList());
|
||||
InPredicate newInPredicate = inPredicate.withChildren(rewrittenChildren);
|
||||
return TypeCoercionUtils.processInPredicate(newInPredicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitInSubquery(InSubquery inSubquery, ExpressionRewriteContext context) {
|
||||
Expression newCompareExpr = inSubquery.getCompareExpr().accept(this, context);
|
||||
Expression newListQuery = inSubquery.getListQuery().accept(this, context);
|
||||
ComparisonPredicate afterTypeCoercion = (ComparisonPredicate) TypeCoercionUtils.processComparisonPredicate(
|
||||
new EqualTo(newCompareExpr, newListQuery));
|
||||
if (newListQuery.getDataType().isBitmapType()) {
|
||||
if (!newCompareExpr.getDataType().isBigIntType()) {
|
||||
newCompareExpr = new Cast(newCompareExpr, BigIntType.INSTANCE);
|
||||
}
|
||||
} else {
|
||||
newCompareExpr = afterTypeCoercion.left();
|
||||
}
|
||||
return new InSubquery(newCompareExpr, (ListQuery) afterTypeCoercion.right(),
|
||||
inSubquery.getCorrelateSlots(), ((ListQuery) afterTypeCoercion.right()).getTypeCoercionExpr(),
|
||||
inSubquery.isNot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitMatch(Match match, ExpressionRewriteContext context) {
|
||||
Expression left = match.left().accept(this, context);
|
||||
Expression right = match.right().accept(this, context);
|
||||
// check child type
|
||||
if (!left.getDataType().isStringLikeType()
|
||||
&& !(left.getDataType() instanceof ArrayType
|
||||
&& ((ArrayType) left.getDataType()).getItemType().isStringLikeType())
|
||||
&& !left.getDataType().isVariantType()) {
|
||||
throw new AnalysisException(String.format(
|
||||
"left operand '%s' part of predicate "
|
||||
+ "'%s' should return type 'STRING', 'ARRAY<STRING> or VARIANT' but "
|
||||
+ "returns type '%s'.",
|
||||
left.toSql(), match.toSql(), left.getDataType()));
|
||||
}
|
||||
|
||||
if (!right.getDataType().isStringLikeType() && !right.getDataType().isNullType()) {
|
||||
throw new AnalysisException(String.format(
|
||||
"right operand '%s' part of predicate " + "'%s' should return type 'STRING' but "
|
||||
+ "returns type '%s'.",
|
||||
right.toSql(), match.toSql(), right.getDataType()));
|
||||
}
|
||||
|
||||
if (left.getDataType().isVariantType()) {
|
||||
left = new Cast(left, right.getDataType());
|
||||
}
|
||||
return match.withChildren(left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitCast(Cast cast, ExpressionRewriteContext context) {
|
||||
cast = (Cast) super.visitCast(cast, context);
|
||||
// NOTICE: just for compatibility with legacy planner.
|
||||
if (cast.child().getDataType().isComplexType() || cast.getDataType().isComplexType()) {
|
||||
TypeCoercionUtils.checkCanCastTo(cast.child().getDataType(), cast.getDataType());
|
||||
}
|
||||
return cast;
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,9 @@ package org.apache.doris.nereids.trees.expressions.functions.udf;
|
||||
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.common.util.ReflectionUtils;
|
||||
import org.apache.doris.nereids.rules.expression.rules.FunctionBinder;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.rules.analysis.ExpressionAnalyzer;
|
||||
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.SlotReference;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
|
||||
@ -28,6 +30,7 @@ import org.apache.doris.nereids.types.DataType;
|
||||
import org.apache.doris.nereids.util.TypeCoercionUtils;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.util.List;
|
||||
@ -80,22 +83,31 @@ public class AliasUdfBuilder extends UdfBuilder {
|
||||
Expression processedExpression = TypeCoercionUtils.processBoundFunction(boundAliasFunction);
|
||||
List<Expression> inputs = processedExpression.getArguments();
|
||||
|
||||
Expression boundFunction = FunctionBinder.INSTANCE.rewrite(aliasUdf.getUnboundFunction(), null);
|
||||
|
||||
// replace the placeholder slot to the input expressions.
|
||||
// adjust input, parameter and replaceMap to be corresponding.
|
||||
Map<String, SlotReference> slots = (boundFunction
|
||||
.<SlotReference>collect(SlotReference.class::isInstance))
|
||||
.stream().collect(Collectors.toMap(SlotReference::getName, k -> k, (v1, v2) -> v2));
|
||||
Map<String, SlotReference> slots = Maps.newLinkedHashMap();
|
||||
aliasUdf.getUnboundFunction().foreachUp(child -> {
|
||||
if (child instanceof SlotReference) {
|
||||
slots.put(((SlotReference) child).getName(), (SlotReference) child);
|
||||
}
|
||||
});
|
||||
|
||||
Map<SlotReference, Expression> replaceMap = Maps.newHashMap();
|
||||
Map<SlotReference, Expression> paramSlotToRealInput = Maps.newHashMap();
|
||||
for (int i = 0; i < inputs.size(); ++i) {
|
||||
String parameter = aliasUdf.getParameters().get(i);
|
||||
Preconditions.checkArgument(slots.containsKey(parameter));
|
||||
replaceMap.put(slots.get(parameter), inputs.get(i));
|
||||
paramSlotToRealInput.put(slots.get(parameter), inputs.get(i));
|
||||
}
|
||||
|
||||
return Pair.of(SlotReplacer.INSTANCE.replace(boundFunction, replaceMap), boundAliasFunction);
|
||||
ExpressionAnalyzer udfAnalyzer = new ExpressionAnalyzer(
|
||||
null, new Scope(ImmutableList.of()), null, false, false) {
|
||||
@Override
|
||||
public Expression visitSlotReference(SlotReference slotReference, ExpressionRewriteContext context) {
|
||||
return paramSlotToRealInput.get(slotReference);
|
||||
}
|
||||
};
|
||||
|
||||
return Pair.of(udfAnalyzer.analyze(aliasUdf.getUnboundFunction()), boundAliasFunction);
|
||||
}
|
||||
|
||||
private static class SlotReplacer extends DefaultExpressionRewriter<Map<SlotReference, Expression>> {
|
||||
|
||||
@ -27,7 +27,7 @@ import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.analyzer.UnboundTableSinkCreator;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.parser.NereidsParser;
|
||||
import org.apache.doris.nereids.rules.analysis.SlotBinder;
|
||||
import org.apache.doris.nereids.rules.analysis.ExpressionAnalyzer;
|
||||
import org.apache.doris.nereids.trees.expressions.EqualTo;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
@ -212,8 +212,8 @@ public class UpdateCommand extends Command implements ForwardWithSync, Explainab
|
||||
throw new AnalysisException("column in assignment list is invalid, " + String.join(".", columnNameParts));
|
||||
}
|
||||
List<String> tableQualifier = RelationUtil.getQualifierName(ctx, nameParts);
|
||||
if (!SlotBinder.sameTableName(tableAlias == null ? tableQualifier.get(2) : tableAlias, tableName)
|
||||
|| (dbName != null && SlotBinder.compareDbName(tableQualifier.get(1), dbName))) {
|
||||
if (!ExpressionAnalyzer.sameTableName(tableAlias == null ? tableQualifier.get(2) : tableAlias, tableName)
|
||||
|| (dbName != null && ExpressionAnalyzer.compareDbName(tableQualifier.get(1), dbName))) {
|
||||
throw new AnalysisException("column in assignment list is invalid, " + String.join(".", columnNameParts));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user