[refactor](nereids) Remove SlotBinder and FunctionBinder (#36872) (#36930)

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:
924060929
2024-06-28 11:21:35 +08:00
committed by GitHub
parent 557da5b24a
commit 69487c4720
12 changed files with 138 additions and 772 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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