[refactor](Nereids) refactor BindSlotReference for easy merge all bind process in one rule (#16156)
This commit is contained in:
@ -20,7 +20,9 @@ package org.apache.doris.nereids;
|
||||
import org.apache.doris.catalog.Database;
|
||||
import org.apache.doris.catalog.Env;
|
||||
import org.apache.doris.catalog.Table;
|
||||
import org.apache.doris.nereids.analyzer.CTEContext;
|
||||
import org.apache.doris.nereids.analyzer.NereidsAnalyzer;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.analyzer.UnboundRelation;
|
||||
import org.apache.doris.nereids.jobs.Job;
|
||||
import org.apache.doris.nereids.jobs.JobContext;
|
||||
@ -36,8 +38,6 @@ import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleFactory;
|
||||
import org.apache.doris.nereids.rules.RuleSet;
|
||||
import org.apache.doris.nereids.rules.analysis.CTEContext;
|
||||
import org.apache.doris.nereids.rules.analysis.Scope;
|
||||
import org.apache.doris.nereids.trees.expressions.SubqueryExpr;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalCTE;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.analysis;
|
||||
package org.apache.doris.nereids.analyzer;
|
||||
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
@ -23,7 +23,6 @@ import org.apache.doris.nereids.jobs.batch.AnalyzeRulesJob;
|
||||
import org.apache.doris.nereids.jobs.batch.AnalyzeSubqueryRulesJob;
|
||||
import org.apache.doris.nereids.jobs.batch.CheckAnalysisJob;
|
||||
import org.apache.doris.nereids.jobs.batch.TypeCoercionJob;
|
||||
import org.apache.doris.nereids.rules.analysis.Scope;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@ -33,6 +32,7 @@ import java.util.Optional;
|
||||
* TODO: revisit the interface after subquery analysis is supported.
|
||||
*/
|
||||
public class NereidsAnalyzer {
|
||||
|
||||
private final CascadesContext cascadesContext;
|
||||
private final Optional<Scope> outerScope;
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.analysis;
|
||||
package org.apache.doris.nereids.analyzer;
|
||||
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.SubqueryExpr;
|
||||
@ -44,6 +44,7 @@ import java.util.Optional;
|
||||
* e.g. select 100, 'value'
|
||||
*/
|
||||
public class UnboundOneRowRelation extends LogicalLeaf implements Unbound, OneRowRelation {
|
||||
|
||||
private final RelationId id;
|
||||
private final List<NamedExpression> projects;
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ import java.util.Optional;
|
||||
* Represent a relation plan node that has not been bound.
|
||||
*/
|
||||
public class UnboundRelation extends LogicalRelation implements Unbound {
|
||||
|
||||
private final List<String> nameParts;
|
||||
private final List<String> partNames;
|
||||
private final boolean isTempPart;
|
||||
|
||||
@ -32,6 +32,7 @@ import java.util.Objects;
|
||||
* Slot has not been bound.
|
||||
*/
|
||||
public class UnboundSlot extends Slot implements Unbound, PropagateNullable {
|
||||
|
||||
private final List<String> nameParts;
|
||||
|
||||
public UnboundSlot(String... nameParts) {
|
||||
@ -39,7 +40,7 @@ public class UnboundSlot extends Slot implements Unbound, PropagateNullable {
|
||||
}
|
||||
|
||||
public UnboundSlot(List<String> nameParts) {
|
||||
this.nameParts = Objects.requireNonNull(nameParts, "nameParts can not be null");
|
||||
this.nameParts = ImmutableList.copyOf(Objects.requireNonNull(nameParts, "nameParts can not be null"));
|
||||
}
|
||||
|
||||
public List<String> getNameParts() {
|
||||
|
||||
@ -33,6 +33,7 @@ import java.util.Objects;
|
||||
* Star expression.
|
||||
*/
|
||||
public class UnboundStar extends NamedExpression implements LeafExpression, Unbound, PropagateNullable {
|
||||
|
||||
private final List<String> qualifier;
|
||||
|
||||
public UnboundStar(List<String> qualifier) {
|
||||
|
||||
@ -24,9 +24,11 @@ import org.apache.doris.nereids.properties.UnboundLogicalProperties;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.TVFProperties;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.table.TableValuedFunction;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.PlanType;
|
||||
import org.apache.doris.nereids.trees.plans.RelationId;
|
||||
import org.apache.doris.nereids.trees.plans.algebra.TVFRelation;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalLeaf;
|
||||
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
|
||||
import org.apache.doris.nereids.util.Utils;
|
||||
@ -36,10 +38,11 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/** UnboundTVFRelation */
|
||||
public class UnboundTVFRelation extends LogicalLeaf implements Relation, Unbound {
|
||||
public class UnboundTVFRelation extends LogicalLeaf implements TVFRelation, Unbound {
|
||||
|
||||
private final RelationId id;
|
||||
private final String functionName;
|
||||
private final TVFProperties properties;
|
||||
private final RelationId id;
|
||||
|
||||
public UnboundTVFRelation(RelationId id, String functionName, TVFProperties properties) {
|
||||
this(id, functionName, properties, Optional.empty(), Optional.empty());
|
||||
@ -65,6 +68,11 @@ public class UnboundTVFRelation extends LogicalLeaf implements Relation, Unbound
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableValuedFunction getFunction() {
|
||||
throw new UnboundException("getFunction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogicalProperties computeLogicalProperties() {
|
||||
return UnboundLogicalProperties.INSTANCE;
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package org.apache.doris.nereids.jobs.batch;
|
||||
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.rules.analysis.AvgDistinctToSumDivCount;
|
||||
import org.apache.doris.nereids.rules.analysis.BindFunction;
|
||||
import org.apache.doris.nereids.rules.analysis.BindRelation;
|
||||
@ -30,7 +31,6 @@ import org.apache.doris.nereids.rules.analysis.ProjectWithDistinctToAggregate;
|
||||
import org.apache.doris.nereids.rules.analysis.RegisterCTE;
|
||||
import org.apache.doris.nereids.rules.analysis.ReplaceExpressionByChildOutput;
|
||||
import org.apache.doris.nereids.rules.analysis.ResolveOrdinalInOrderByAndGroupBy;
|
||||
import org.apache.doris.nereids.rules.analysis.Scope;
|
||||
import org.apache.doris.nereids.rules.analysis.UserAuthentication;
|
||||
import org.apache.doris.nereids.rules.expression.rewrite.ExpressionNormalization;
|
||||
import org.apache.doris.nereids.rules.expression.rewrite.rules.CharacterLiteralTypeCoercion;
|
||||
|
||||
@ -20,13 +20,13 @@ package org.apache.doris.nereids.memo;
|
||||
import org.apache.doris.common.IdGenerator;
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.StatementContext;
|
||||
import org.apache.doris.nereids.analyzer.CTEContext;
|
||||
import org.apache.doris.nereids.metrics.EventChannel;
|
||||
import org.apache.doris.nereids.metrics.EventProducer;
|
||||
import org.apache.doris.nereids.metrics.consumer.LogConsumer;
|
||||
import org.apache.doris.nereids.metrics.event.GroupMergeEvent;
|
||||
import org.apache.doris.nereids.properties.LogicalProperties;
|
||||
import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.rules.analysis.CTEContext;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
|
||||
@ -444,7 +444,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
}
|
||||
|
||||
private LogicalPlan withCheckPolicy(LogicalPlan plan) {
|
||||
return new LogicalCheckPolicy(plan);
|
||||
return new LogicalCheckPolicy<>(plan);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1037,7 +1037,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
public Literal visitStringLiteral(StringLiteralContext ctx) {
|
||||
// TODO: add unescapeSQLString.
|
||||
String txt = ctx.STRING().getText();
|
||||
String s = txt.substring(1, txt.length() - 1);
|
||||
String s = escapeBackSlash(txt.substring(1, txt.length() - 1));
|
||||
return new VarcharLiteral(s);
|
||||
}
|
||||
|
||||
@ -1334,6 +1334,8 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
joinType = JoinType.RIGHT_OUTER_JOIN;
|
||||
} else if (join.joinType().INNER() != null) {
|
||||
joinType = JoinType.INNER_JOIN;
|
||||
} else if (join.joinCriteria() != null) {
|
||||
joinType = JoinType.INNER_JOIN;
|
||||
} else {
|
||||
joinType = JoinType.CROSS_JOIN;
|
||||
}
|
||||
@ -1347,10 +1349,10 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
throw new ParseException("Invalid join hint: " + hint, hintCtx);
|
||||
}
|
||||
}).orElse(JoinHint.NONE);
|
||||
// TODO: natural join, lateral join, using join, union join
|
||||
// TODO: natural join, lateral join, union join
|
||||
JoinCriteriaContext joinCriteria = join.joinCriteria();
|
||||
Optional<Expression> condition = Optional.empty();
|
||||
List<UnboundSlot> ids = null;
|
||||
List<Expression> ids = null;
|
||||
if (joinCriteria != null) {
|
||||
if (join.joinType().CROSS() != null) {
|
||||
throw new ParseException("Cross join can't be used with ON clause", joinCriteria);
|
||||
@ -1376,7 +1378,9 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
last,
|
||||
plan(join.relationPrimary()));
|
||||
} else {
|
||||
last = new UsingJoin(joinType, last, plan(join.relationPrimary()), ImmutableList.of(), ids, joinHint);
|
||||
last = new UsingJoin<>(joinType, last,
|
||||
plan(join.relationPrimary()), ImmutableList.of(), ids, joinHint);
|
||||
|
||||
}
|
||||
}
|
||||
return last;
|
||||
|
||||
@ -19,7 +19,7 @@ package org.apache.doris.nereids.pattern;
|
||||
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.StatementContext;
|
||||
import org.apache.doris.nereids.rules.analysis.CTEContext;
|
||||
import org.apache.doris.nereids.analyzer.CTEContext;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ import org.apache.doris.catalog.external.HMSExternalTable;
|
||||
import org.apache.doris.common.util.Util;
|
||||
import org.apache.doris.datasource.CatalogIf;
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.CTEContext;
|
||||
import org.apache.doris.nereids.analyzer.UnboundRelation;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.memo.Memo;
|
||||
|
||||
@ -19,35 +19,25 @@ package org.apache.doris.nereids.rules.analysis;
|
||||
|
||||
import org.apache.doris.catalog.BuiltinAggregateFunctions;
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.UnboundAlias;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.analyzer.UnboundFunction;
|
||||
import org.apache.doris.nereids.analyzer.UnboundOneRowRelation;
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.analyzer.UnboundStar;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.memo.Memo;
|
||||
import org.apache.doris.nereids.properties.OrderKey;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.rules.analysis.BindFunction.FunctionBinder;
|
||||
import org.apache.doris.nereids.trees.UnaryNode;
|
||||
import org.apache.doris.nereids.trees.expressions.Alias;
|
||||
import org.apache.doris.nereids.trees.expressions.BoundStar;
|
||||
import org.apache.doris.nereids.trees.expressions.EqualTo;
|
||||
import org.apache.doris.nereids.trees.expressions.Exists;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.InSubquery;
|
||||
import org.apache.doris.nereids.trees.expressions.ListQuery;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
import org.apache.doris.nereids.trees.expressions.Not;
|
||||
import org.apache.doris.nereids.trees.expressions.ScalarSubquery;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.SlotReference;
|
||||
import org.apache.doris.nereids.trees.expressions.SubqueryExpr;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.ExpressionTrait;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.Function;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
|
||||
import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
|
||||
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.trees.plans.LeafPlan;
|
||||
@ -68,13 +58,11 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalRepeat;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSetOperation;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
|
||||
import org.apache.doris.nereids.trees.plans.logical.UsingJoin;
|
||||
import org.apache.doris.planner.PlannerContext;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -115,85 +103,83 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
RuleType.BINDING_PROJECT_SLOT.build(
|
||||
logicalProject().when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalProject<GroupPlan> project = ctx.root;
|
||||
List<NamedExpression> boundSlots =
|
||||
bind(project.getProjects(), project.children(), project, ctx.cascadesContext);
|
||||
List<NamedExpression> exceptSlots = bind(project.getExcepts(), project.children(), project,
|
||||
List<NamedExpression> boundProjections =
|
||||
bind(project.getProjects(), project.children(), ctx.cascadesContext);
|
||||
List<NamedExpression> boundExceptions = bind(project.getExcepts(), project.children(),
|
||||
ctx.cascadesContext);
|
||||
List<NamedExpression> newOutput = flatBoundStar(adjustNullableForProjects(project, boundSlots));
|
||||
newOutput.removeAll(exceptSlots);
|
||||
return new LogicalProject<>(newOutput, project.child(), project.isDistinct());
|
||||
boundProjections = flatBoundStar(boundProjections, boundExceptions);
|
||||
return new LogicalProject<>(boundProjections, project.child(), project.isDistinct());
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_FILTER_SLOT.build(
|
||||
logicalFilter().when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalFilter<GroupPlan> filter = ctx.root;
|
||||
Set<Expression> boundConjuncts
|
||||
= bind(filter.getConjuncts(), filter.children(), filter, ctx.cascadesContext);
|
||||
= bind(filter.getConjuncts(), filter.children(), ctx.cascadesContext);
|
||||
return new LogicalFilter<>(boundConjuncts, filter.child());
|
||||
})
|
||||
),
|
||||
|
||||
RuleType.BINDING_USING_JOIN_SLOT.build(
|
||||
usingJoin().thenApply(ctx -> {
|
||||
UsingJoin<GroupPlan, GroupPlan> using = ctx.root;
|
||||
LogicalJoin lj = new LogicalJoin(using.getJoinType() == JoinType.CROSS_JOIN
|
||||
? JoinType.INNER_JOIN : using.getJoinType(),
|
||||
using.getHashJoinConjuncts(),
|
||||
using.getOtherJoinConjuncts(), using.getHint(), using.left(),
|
||||
using.right());
|
||||
List<Expression> unboundSlots = lj.getHashJoinConjuncts();
|
||||
Set<String> slotNames = new HashSet<>();
|
||||
List<Slot> leftOutput = new ArrayList<>(lj.left().getOutput());
|
||||
// Suppose A JOIN B USING(name) JOIN C USING(name), [A JOIN B] is the left node, in this case,
|
||||
// C should combine with table B on C.name=B.name. so we reverse the output to make sure that
|
||||
// the most right slot is matched with priority.
|
||||
Collections.reverse(leftOutput);
|
||||
List<Expression> leftSlots = new ArrayList<>();
|
||||
Scope scope = toScope(leftOutput.stream()
|
||||
.filter(s -> !slotNames.contains(s.getName()))
|
||||
.peek(s -> slotNames.add(s.getName())).collect(
|
||||
Collectors.toList()));
|
||||
for (Expression unboundSlot : unboundSlots) {
|
||||
Expression expression = new SlotBinder(scope, ctx.cascadesContext).bind(unboundSlot);
|
||||
leftSlots.add(expression);
|
||||
}
|
||||
slotNames.clear();
|
||||
scope = toScope(lj.right().getOutput().stream()
|
||||
.filter(s -> !slotNames.contains(s.getName()))
|
||||
.peek(s -> slotNames.add(s.getName())).collect(
|
||||
Collectors.toList()));
|
||||
List<Expression> rightSlots = new ArrayList<>();
|
||||
for (Expression unboundSlot : unboundSlots) {
|
||||
Expression expression = new SlotBinder(scope, ctx.cascadesContext).bind(unboundSlot);
|
||||
rightSlots.add(expression);
|
||||
}
|
||||
int size = leftSlots.size();
|
||||
List<Expression> hashEqExpr = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
hashEqExpr.add(new EqualTo(leftSlots.get(i), rightSlots.get(i)));
|
||||
}
|
||||
return lj.withHashJoinConjuncts(hashEqExpr);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_JOIN_SLOT.build(
|
||||
logicalJoin().when(Plan::canBind)
|
||||
.whenNot(j -> j.getJoinType().equals(JoinType.USING_JOIN)).thenApply(ctx -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> join = ctx.root;
|
||||
List<Expression> cond = join.getOtherJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
List<Expression> hashJoinConjuncts = join.getHashJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
return new LogicalJoin<>(join.getJoinType(),
|
||||
hashJoinConjuncts, cond, join.getHint(), join.left(), join.right());
|
||||
})
|
||||
),
|
||||
usingJoin().thenApply(ctx -> {
|
||||
UsingJoin<GroupPlan, GroupPlan> using = ctx.root;
|
||||
LogicalJoin<Plan, Plan> lj = new LogicalJoin<>(using.getJoinType() == JoinType.CROSS_JOIN
|
||||
? JoinType.INNER_JOIN : using.getJoinType(),
|
||||
using.getHashJoinConjuncts(),
|
||||
using.getOtherJoinConjuncts(), using.getHint(), using.left(),
|
||||
using.right());
|
||||
List<Expression> unboundSlots = lj.getHashJoinConjuncts();
|
||||
Set<String> slotNames = new HashSet<>();
|
||||
List<Slot> leftOutput = new ArrayList<>(lj.left().getOutput());
|
||||
// Suppose A JOIN B USING(name) JOIN C USING(name), [A JOIN B] is the left node, in this case,
|
||||
// C should combine with table B on C.name=B.name. so we reverse the output to make sure that
|
||||
// the most right slot is matched with priority.
|
||||
Collections.reverse(leftOutput);
|
||||
List<Expression> leftSlots = new ArrayList<>();
|
||||
Scope scope = toScope(leftOutput.stream()
|
||||
.filter(s -> !slotNames.contains(s.getName()))
|
||||
.peek(s -> slotNames.add(s.getName()))
|
||||
.collect(Collectors.toList()));
|
||||
for (Expression unboundSlot : unboundSlots) {
|
||||
Expression expression = new Binder(scope, ctx.cascadesContext).bind(unboundSlot);
|
||||
leftSlots.add(expression);
|
||||
}
|
||||
slotNames.clear();
|
||||
scope = toScope(lj.right().getOutput().stream()
|
||||
.filter(s -> !slotNames.contains(s.getName()))
|
||||
.peek(s -> slotNames.add(s.getName()))
|
||||
.collect(Collectors.toList()));
|
||||
List<Expression> rightSlots = new ArrayList<>();
|
||||
for (Expression unboundSlot : unboundSlots) {
|
||||
Expression expression = new Binder(scope, ctx.cascadesContext).bind(unboundSlot);
|
||||
rightSlots.add(expression);
|
||||
}
|
||||
int size = leftSlots.size();
|
||||
List<Expression> hashEqExpr = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
hashEqExpr.add(new EqualTo(leftSlots.get(i), rightSlots.get(i)));
|
||||
}
|
||||
return lj.withHashJoinConjuncts(hashEqExpr);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_JOIN_SLOT.build(
|
||||
logicalJoin().when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> join = ctx.root;
|
||||
List<Expression> cond = join.getOtherJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
List<Expression> hashJoinConjuncts = join.getHashJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
return new LogicalJoin<>(join.getJoinType(),
|
||||
hashJoinConjuncts, cond, join.getHint(), join.left(), join.right());
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_AGGREGATE_SLOT.build(
|
||||
logicalAggregate().when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalAggregate<GroupPlan> agg = ctx.root;
|
||||
List<NamedExpression> output =
|
||||
bind(agg.getOutputExpressions(), agg.children(), agg, ctx.cascadesContext);
|
||||
bind(agg.getOutputExpressions(), agg.children(), ctx.cascadesContext);
|
||||
|
||||
// The columns referenced in group by are first obtained from the child's output,
|
||||
// and then from the node's output
|
||||
@ -244,12 +230,14 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
group by a
|
||||
group_by_key is bound on t1.a
|
||||
*/
|
||||
duplicatedSlotNames.stream().forEach(dup -> childOutputsToExpr.remove(dup));
|
||||
Map<String, Expression> aliasNameToExpr = output.stream()
|
||||
duplicatedSlotNames.forEach(childOutputsToExpr::remove);
|
||||
output.stream()
|
||||
.filter(ne -> ne instanceof Alias)
|
||||
.map(Alias.class::cast)
|
||||
//agg function cannot be bound with group_by_key
|
||||
.filter(alias -> ! alias.child().anyMatch(expr -> {
|
||||
// agg function cannot be bound with group_by_key
|
||||
// TODO(morrySnow): after bind function moved here,
|
||||
// we could just use instanceof AggregateFunction
|
||||
.filter(alias -> !alias.child().anyMatch(expr -> {
|
||||
if (expr instanceof UnboundFunction) {
|
||||
UnboundFunction unboundFunction = (UnboundFunction) expr;
|
||||
return BuiltinAggregateFunctions.INSTANCE.aggFuncNames.contains(
|
||||
@ -258,9 +246,7 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
return false;
|
||||
}
|
||||
))
|
||||
.collect(Collectors.toMap(Alias::getName, UnaryNode::child, (oldExpr, newExpr) -> oldExpr));
|
||||
aliasNameToExpr.entrySet().stream()
|
||||
.forEach(e -> childOutputsToExpr.putIfAbsent(e.getKey(), e.getValue()));
|
||||
.forEach(alias -> childOutputsToExpr.putIfAbsent(alias.getName(), alias.child()));
|
||||
|
||||
List<Expression> replacedGroupBy = agg.getGroupByExpressions().stream()
|
||||
.map(groupBy -> {
|
||||
@ -282,9 +268,9 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
Set<Slot> outputSlots = output.stream()
|
||||
.filter(SlotReference.class::isInstance)
|
||||
.peek(slot -> outputSlotNames.add(slot.getName()))
|
||||
.map(NamedExpression::toSlot).collect(
|
||||
Collectors.toSet());
|
||||
//suppose group by key is a.
|
||||
.map(NamedExpression::toSlot)
|
||||
.collect(Collectors.toSet());
|
||||
// suppose group by key is a.
|
||||
// if both t1.a and t2.a are in agg.child.output, and t1.a in agg.output,
|
||||
// bind group_by_key a with t1.a
|
||||
// ` .filter(slot -> !outputSlotNames.contains(slot.getName()))`
|
||||
@ -294,9 +280,9 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
boundSlots.addAll(outputSlots);
|
||||
SlotBinder binder = new SlotBinder(toScope(Lists.newArrayList(boundSlots)), ctx.cascadesContext);
|
||||
SlotBinder childBinder
|
||||
= new SlotBinder(toScope(new ArrayList<>(agg.child().getOutputSet())), ctx.cascadesContext);
|
||||
Binder binder = new Binder(toScope(Lists.newArrayList(boundSlots)), ctx.cascadesContext);
|
||||
Binder childBinder
|
||||
= new Binder(toScope(new ArrayList<>(agg.child().getOutputSet())), ctx.cascadesContext);
|
||||
|
||||
List<Expression> groupBy = replacedGroupBy.stream()
|
||||
.map(expression -> {
|
||||
@ -308,21 +294,18 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
List<Expression> unboundGroupBys = Lists.newArrayList();
|
||||
Predicate<List<Expression>> hasUnBound = (exprs) -> {
|
||||
return exprs.stream().anyMatch(
|
||||
expression -> {
|
||||
if (expression.anyMatch(UnboundSlot.class::isInstance)) {
|
||||
unboundGroupBys.add(expression);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
Predicate<List<Expression>> hasUnBound = (exprs) -> exprs.stream().anyMatch(
|
||||
expression -> {
|
||||
if (expression.anyMatch(UnboundSlot.class::isInstance)) {
|
||||
unboundGroupBys.add(expression);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (hasUnBound.test(groupBy)) {
|
||||
throw new AnalysisException("cannot bind GROUP BY KEY: " + unboundGroupBys.get(0).toSql());
|
||||
}
|
||||
List<NamedExpression> newOutput = adjustNullableForAgg(agg, output);
|
||||
return agg.withGroupByAndOutput(groupBy, newOutput);
|
||||
return agg.withGroupByAndOutput(groupBy, output);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_REPEAT_SLOT.build(
|
||||
@ -330,7 +313,7 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
LogicalRepeat<GroupPlan> repeat = ctx.root;
|
||||
|
||||
List<NamedExpression> output =
|
||||
bind(repeat.getOutputExpressions(), repeat.children(), repeat, ctx.cascadesContext);
|
||||
bind(repeat.getOutputExpressions(), repeat.children(), ctx.cascadesContext);
|
||||
|
||||
// The columns referenced in group by are first obtained from the child's output,
|
||||
// and then from the node's output
|
||||
@ -340,8 +323,7 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
.filter(ne -> ne instanceof Alias)
|
||||
.map(Alias.class::cast)
|
||||
.collect(Collectors.toMap(Alias::getName, UnaryNode::child, (oldExpr, newExpr) -> oldExpr));
|
||||
aliasNameToExpr.entrySet().stream()
|
||||
.forEach(e -> childOutputsToExpr.putIfAbsent(e.getKey(), e.getValue()));
|
||||
aliasNameToExpr.forEach(childOutputsToExpr::putIfAbsent);
|
||||
|
||||
List<List<Expression>> replacedGroupingSets = repeat.getGroupingSets().stream()
|
||||
.map(groupBy ->
|
||||
@ -361,7 +343,7 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
|
||||
List<List<Expression>> groupingSets = replacedGroupingSets
|
||||
.stream()
|
||||
.map(groupingSet -> bind(groupingSet, repeat.children(), repeat, ctx.cascadesContext))
|
||||
.map(groupingSet -> bind(groupingSet, repeat.children(), ctx.cascadesContext))
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
List<NamedExpression> newOutput = adjustNullableForRepeat(groupingSets, output);
|
||||
return repeat.withGroupSetsAndOutput(groupingSets, newOutput);
|
||||
@ -371,53 +353,28 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
logicalSort(aggregate()).when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalSort<Aggregate<GroupPlan>> sort = ctx.root;
|
||||
Aggregate<GroupPlan> aggregate = sort.child();
|
||||
return bindSortWithAggregateFunction(sort, aggregate, ctx.cascadesContext);
|
||||
return bindSort(sort, aggregate, ctx.cascadesContext);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_SORT_SLOT.build(
|
||||
logicalSort(logicalHaving(aggregate())).when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalSort<LogicalHaving<Aggregate<GroupPlan>>> sort = ctx.root;
|
||||
Aggregate<GroupPlan> aggregate = sort.child().child();
|
||||
return bindSortWithAggregateFunction(sort, aggregate, ctx.cascadesContext);
|
||||
return bindSort(sort, aggregate, ctx.cascadesContext);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_SORT_SLOT.build(
|
||||
logicalSort(logicalHaving(logicalProject())).when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalSort<LogicalHaving<LogicalProject<GroupPlan>>> sort = ctx.root;
|
||||
List<OrderKey> sortItemList = sort.getOrderKeys()
|
||||
.stream()
|
||||
.map(orderKey -> {
|
||||
Expression item = bind(orderKey.getExpr(), sort.children(), ctx.cascadesContext);
|
||||
if (item.containsType(UnboundSlot.class)) {
|
||||
item = bind(item, sort.child().children(), ctx.cascadesContext);
|
||||
}
|
||||
return new OrderKey(item, orderKey.isAsc(), orderKey.isNullFirst());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new LogicalSort<>(sortItemList, sort.child());
|
||||
LogicalProject<GroupPlan> project = sort.child().child();
|
||||
return bindSort(sort, project, ctx.cascadesContext);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_SORT_SLOT.build(
|
||||
logicalSort(logicalProject()).when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalSort<LogicalProject<GroupPlan>> sort = ctx.root;
|
||||
Set<Slot> projectOutput = sort.child().getOutputSet();
|
||||
SlotBinder binderOnProject = new SlotBinder(toScope(Lists.newArrayList(projectOutput)),
|
||||
ctx.cascadesContext);
|
||||
Set<Slot> projectChildrenOutput = sort.child().children().stream()
|
||||
.flatMap(plan -> plan.getOutputSet().stream())
|
||||
.collect(Collectors.toSet());
|
||||
SlotBinder binderOnProjectChild = new SlotBinder(
|
||||
toScope(Lists.newArrayList(projectChildrenOutput)),
|
||||
ctx.cascadesContext);
|
||||
List<OrderKey> sortItemList = sort.getOrderKeys()
|
||||
.stream()
|
||||
.map(orderKey -> {
|
||||
Expression item = binderOnProject.bind(orderKey.getExpr());
|
||||
item = binderOnProjectChild.bind(item);
|
||||
return new OrderKey(item, orderKey.isAsc(), orderKey.isNullFirst());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new LogicalSort<>(sortItemList, sort.child());
|
||||
LogicalProject<GroupPlan> project = sort.child();
|
||||
return bindSort(sort, project, ctx.cascadesContext);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_SORT_SET_OPERATION_SLOT.build(
|
||||
@ -426,13 +383,9 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
List<OrderKey> sortItemList = sort.getOrderKeys()
|
||||
.stream()
|
||||
.map(orderKey -> {
|
||||
Expression item = bind(orderKey.getExpr(), sort.children(), ctx.cascadesContext);
|
||||
if (item.containsType(UnboundSlot.class)) {
|
||||
item = bind(item, sort.child().children(), ctx.cascadesContext);
|
||||
}
|
||||
Expression item = bind(orderKey.getExpr(), sort.child(), ctx.cascadesContext);
|
||||
return new OrderKey(item, orderKey.isAsc(), orderKey.isNullFirst());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new LogicalSort<>(sortItemList, sort.child());
|
||||
})
|
||||
),
|
||||
@ -440,21 +393,10 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
logicalHaving(aggregate()).when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalHaving<Aggregate<GroupPlan>> having = ctx.root;
|
||||
Plan childPlan = having.child();
|
||||
// We should deduplicate the slots, otherwise the binding process will fail due to the
|
||||
// ambiguous slots exist.
|
||||
List<Slot> childChildSlots = childPlan.children().stream()
|
||||
.flatMap(plan -> plan.getOutputSet().stream())
|
||||
.collect(Collectors.toList());
|
||||
SlotBinder childChildBinder = new SlotBinder(toScope(childChildSlots),
|
||||
ctx.cascadesContext);
|
||||
List<Slot> childSlots = childPlan.getOutputSet().stream()
|
||||
.collect(Collectors.toList());
|
||||
SlotBinder childBinder = new SlotBinder(toScope(childSlots),
|
||||
ctx.cascadesContext);
|
||||
Set<Expression> boundConjuncts = having.getConjuncts().stream().map(
|
||||
expr -> {
|
||||
expr = childChildBinder.bind(expr);
|
||||
return childBinder.bind(expr);
|
||||
expr = bind(expr, childPlan.children(), ctx.cascadesContext);
|
||||
return bind(expr, childPlan, ctx.cascadesContext);
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
return new LogicalHaving<>(boundConjuncts, having.child());
|
||||
@ -464,21 +406,10 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
logicalHaving(any()).when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalHaving<Plan> having = ctx.root;
|
||||
Plan childPlan = having.child();
|
||||
// We should deduplicate the slots, otherwise the binding process will fail due to the
|
||||
// ambiguous slots exist.
|
||||
List<Slot> childChildSlots = childPlan.children().stream()
|
||||
.flatMap(plan -> plan.getOutputSet().stream())
|
||||
.collect(Collectors.toList());
|
||||
SlotBinder childChildBinder = new SlotBinder(toScope(childChildSlots),
|
||||
ctx.cascadesContext);
|
||||
List<Slot> childSlots = childPlan.getOutputSet().stream()
|
||||
.collect(Collectors.toList());
|
||||
SlotBinder childBinder = new SlotBinder(toScope(childSlots),
|
||||
ctx.cascadesContext);
|
||||
Set<Expression> boundConjuncts = having.getConjuncts().stream().map(
|
||||
expr -> {
|
||||
expr = childBinder.bind(expr);
|
||||
return childChildBinder.bind(expr);
|
||||
expr = bind(expr, childPlan, ctx.cascadesContext);
|
||||
return bind(expr, childPlan.children(), ctx.cascadesContext);
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
return new LogicalHaving<>(boundConjuncts, having.child());
|
||||
@ -506,10 +437,10 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
+ setOperation.child(1).getOutput().size() + " column(s)");
|
||||
}
|
||||
|
||||
// INTERSECT and EXCEPT does not support ALL qualifie
|
||||
// INTERSECT and EXCEPT does not support ALL qualified
|
||||
if (setOperation.getQualifier() == Qualifier.ALL
|
||||
&& (setOperation instanceof LogicalExcept || setOperation instanceof LogicalIntersect)) {
|
||||
throw new AnalysisException("INTERSECT and EXCEPT does not support ALL qualifie");
|
||||
throw new AnalysisException("INTERSECT and EXCEPT does not support ALL qualified");
|
||||
}
|
||||
|
||||
List<List<Expression>> castExpressions = setOperation.collectCastExpressions();
|
||||
@ -521,8 +452,8 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
RuleType.BINDING_GENERATE_SLOT.build(
|
||||
logicalGenerate().when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalGenerate<GroupPlan> generate = ctx.root;
|
||||
List<Function> boundSlotGenerators = bind(generate.getGenerators(), generate.children(),
|
||||
generate, ctx.cascadesContext);
|
||||
List<Function> boundSlotGenerators
|
||||
= bind(generate.getGenerators(), generate.children(), ctx.cascadesContext);
|
||||
List<Function> boundFunctionGenerators = boundSlotGenerators.stream()
|
||||
.map(f -> FunctionBinder.INSTANCE.bindTableGeneratingFunction(
|
||||
(UnboundFunction) f, ctx.statementContext))
|
||||
@ -551,8 +482,8 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
);
|
||||
}
|
||||
|
||||
private Plan bindSortWithAggregateFunction(
|
||||
LogicalSort<? extends Plan> sort, Aggregate<? extends Plan> aggregate, CascadesContext ctx) {
|
||||
private Plan bindSort(
|
||||
LogicalSort<? extends Plan> sort, Plan plan, CascadesContext ctx) {
|
||||
// 1. We should deduplicate the slots, otherwise the binding process will fail due to the
|
||||
// ambiguous slots exist.
|
||||
// 2. try to bound order-key with agg output, if failed, try to bound with output of agg.child
|
||||
@ -568,21 +499,19 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
// group by col1
|
||||
// order by col1; # order by order_col1
|
||||
// bind order_col1 with alias_col1, then, bind it with inner_col1
|
||||
SlotBinder outputBinder = new SlotBinder(
|
||||
toScope(aggregate.getOutputSet().stream().collect(Collectors.toList())), ctx);
|
||||
List<Slot> childOutputSlots = aggregate.child().getOutputSet().stream().collect(Collectors.toList());
|
||||
SlotBinder childOutputBinder = new SlotBinder(toScope(childOutputSlots), ctx);
|
||||
List<OrderKey> sortItemList = sort.getOrderKeys()
|
||||
.stream()
|
||||
.map(orderKey -> {
|
||||
Expression item = outputBinder.bind(orderKey.getExpr());
|
||||
item = childOutputBinder.bind(item);
|
||||
Expression item = bind(orderKey.getExpr(), plan, ctx);
|
||||
item = bind(item, plan.children(), ctx);
|
||||
return new OrderKey(item, orderKey.isAsc(), orderKey.isNullFirst());
|
||||
}).collect(Collectors.toList());
|
||||
return new LogicalSort<>(sortItemList, sort.child());
|
||||
}
|
||||
|
||||
private List<NamedExpression> flatBoundStar(List<NamedExpression> boundSlots) {
|
||||
private List<NamedExpression> flatBoundStar(
|
||||
List<NamedExpression> boundSlots,
|
||||
List<NamedExpression> boundExceptions) {
|
||||
return boundSlots
|
||||
.stream()
|
||||
.flatMap(slot -> {
|
||||
@ -591,395 +520,34 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
} else {
|
||||
return Stream.of(slot);
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
})
|
||||
.filter(s -> !boundExceptions.contains(s))
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
private <E extends Expression> List<E> bind(List<E> exprList, List<Plan> inputs, Plan plan,
|
||||
CascadesContext cascadesContext) {
|
||||
private <E extends Expression> List<E> bind(List<E> exprList, List<Plan> inputs, CascadesContext cascadesContext) {
|
||||
return exprList.stream()
|
||||
.map(expr -> bind(expr, inputs, cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <E extends Expression> Set<E> bind(Set<E> exprList, List<Plan> inputs, Plan plan,
|
||||
CascadesContext cascadesContext) {
|
||||
private <E extends Expression> Set<E> bind(Set<E> exprList, List<Plan> inputs, CascadesContext cascadesContext) {
|
||||
return exprList.stream()
|
||||
.map(expr -> bind(expr, inputs, cascadesContext))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends Expression> E bind(E expr, Plan input, CascadesContext cascadesContext) {
|
||||
return (E) new Binder(toScope(input.getOutput()), cascadesContext).bind(expr);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends Expression> E bind(E expr, List<Plan> inputs, CascadesContext cascadesContext) {
|
||||
List<Slot> boundedSlots = inputs.stream()
|
||||
.flatMap(input -> input.getOutput().stream())
|
||||
.collect(Collectors.toList());
|
||||
return (E) new SlotBinder(toScope(boundedSlots), cascadesContext).bind(expr);
|
||||
}
|
||||
|
||||
private class SlotBinder extends SubExprAnalyzer {
|
||||
|
||||
public SlotBinder(Scope scope, CascadesContext cascadesContext) {
|
||||
super(scope, cascadesContext);
|
||||
}
|
||||
|
||||
public Expression bind(Expression expression) {
|
||||
return expression.accept(this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundAlias(UnboundAlias unboundAlias, PlannerContext context) {
|
||||
Expression child = unboundAlias.child().accept(this, context);
|
||||
if (unboundAlias.getAlias().isPresent()) {
|
||||
return new Alias(child, unboundAlias.getAlias().get());
|
||||
}
|
||||
if (child instanceof NamedExpression) {
|
||||
return new Alias(child, ((NamedExpression) child).getName());
|
||||
} else {
|
||||
// TODO: resolve aliases
|
||||
return new Alias(child, child.toSql());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slot visitUnboundSlot(UnboundSlot unboundSlot, PlannerContext 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 (!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().add(bounded.get(0));
|
||||
}
|
||||
return bounded.get(0);
|
||||
default:
|
||||
/*
|
||||
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, PlannerContext context) {
|
||||
List<String> qualifier = unboundStar.getQualifier();
|
||||
List<Slot> slots = getScope().getSlots()
|
||||
.stream()
|
||||
.filter(slot -> !(slot instanceof SlotReference) || ((SlotReference) slot).isVisible())
|
||||
.collect(Collectors.toList());
|
||||
switch (qualifier.size()) {
|
||||
case 0: // select *
|
||||
return new BoundStar(slots);
|
||||
case 1: // select table.*
|
||||
case 2: // select 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));
|
||||
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 qualifierStar.get(0).equalsIgnoreCase(boundSlotQualifier.get(0))
|
||||
&& qualifierStar.get(1).equalsIgnoreCase(boundSlotQualifier.get(1));
|
||||
default:
|
||||
throw new AnalysisException("Not supported qualifier: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
default:
|
||||
throw new AnalysisException("Not supported name: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new BoundStar(slots);
|
||||
}
|
||||
|
||||
private List<Slot> bindSlot(UnboundSlot unboundSlot, List<Slot> boundSlots) {
|
||||
return boundSlots.stream().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 qualifierDbName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
return qualifierDbName.equalsIgnoreCase(nameParts.get(0))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(1));
|
||||
} else if (nameParts.size() == 3) {
|
||||
String qualifierDbName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
String qualifierClusterName = boundSlot.getQualifier().get(qualifierSize - 2);
|
||||
return qualifierClusterName.equalsIgnoreCase(nameParts.get(0))
|
||||
&& qualifierDbName.equalsIgnoreCase(nameParts.get(1))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(2));
|
||||
}
|
||||
//TODO: handle name parts more than three.
|
||||
throw new AnalysisException("Not supported name: "
|
||||
+ StringUtils.join(nameParts, "."));
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
/** BoundStar is used to wrap list of slots for temporary. */
|
||||
public static class BoundStar extends NamedExpression implements PropagateNullable {
|
||||
public BoundStar(List<Slot> children) {
|
||||
super(children.toArray(new Slot[0]));
|
||||
Preconditions.checkArgument(children.stream().noneMatch(slot -> slot instanceof UnboundSlot),
|
||||
"BoundStar can not wrap UnboundSlot"
|
||||
);
|
||||
}
|
||||
|
||||
public String toSql() {
|
||||
return children.stream().map(Expression::toSql).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public List<Slot> getSlots() {
|
||||
return (List) children();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
|
||||
return visitor.visitBoundStar(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the visitor to iterate sub expression.
|
||||
*/
|
||||
private static class SubExprAnalyzer extends DefaultExpressionRewriter<PlannerContext> {
|
||||
private final Scope scope;
|
||||
private final CascadesContext cascadesContext;
|
||||
|
||||
public SubExprAnalyzer(Scope scope, CascadesContext cascadesContext) {
|
||||
this.scope = scope;
|
||||
this.cascadesContext = cascadesContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitNot(Not not, PlannerContext context) {
|
||||
Expression child = not.child();
|
||||
if (child instanceof Exists) {
|
||||
return visitExistsSubquery(
|
||||
new Exists(((Exists) child).getQueryPlan(), true), context);
|
||||
} else if (child instanceof InSubquery) {
|
||||
return visitInSubquery(new InSubquery(((InSubquery) child).getCompareExpr(),
|
||||
((InSubquery) child).getListQuery(), true), context);
|
||||
}
|
||||
return visit(not, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitExistsSubquery(Exists exists, PlannerContext context) {
|
||||
AnalyzedResult analyzedResult = analyzeSubquery(exists);
|
||||
|
||||
return new Exists(analyzedResult.getLogicalPlan(),
|
||||
analyzedResult.getCorrelatedSlots(), exists.isNot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitInSubquery(InSubquery expr, PlannerContext context) {
|
||||
AnalyzedResult analyzedResult = analyzeSubquery(expr);
|
||||
|
||||
checkOutputColumn(analyzedResult.getLogicalPlan());
|
||||
checkHasGroupBy(analyzedResult);
|
||||
|
||||
return new InSubquery(
|
||||
expr.getCompareExpr().accept(this, context),
|
||||
new ListQuery(analyzedResult.getLogicalPlan()),
|
||||
analyzedResult.getCorrelatedSlots(), expr.isNot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitScalarSubquery(ScalarSubquery scalar, PlannerContext context) {
|
||||
AnalyzedResult analyzedResult = analyzeSubquery(scalar);
|
||||
|
||||
checkOutputColumn(analyzedResult.getLogicalPlan());
|
||||
checkRootIsAgg(analyzedResult);
|
||||
checkHasGroupBy(analyzedResult);
|
||||
|
||||
return new ScalarSubquery(analyzedResult.getLogicalPlan(), analyzedResult.getCorrelatedSlots());
|
||||
}
|
||||
|
||||
private void checkOutputColumn(LogicalPlan plan) {
|
||||
if (plan.getOutput().size() != 1) {
|
||||
throw new AnalysisException("Multiple columns returned by subquery are not yet supported. Found "
|
||||
+ plan.getOutput().size());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRootIsAgg(AnalyzedResult analyzedResult) {
|
||||
if (!analyzedResult.isCorrelated()) {
|
||||
return;
|
||||
}
|
||||
if (!analyzedResult.rootIsAgg()) {
|
||||
throw new AnalysisException("The select item in correlated subquery of binary predicate "
|
||||
+ "should only be sum, min, max, avg and count. Current subquery: "
|
||||
+ analyzedResult.getLogicalPlan());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkHasGroupBy(AnalyzedResult analyzedResult) {
|
||||
if (!analyzedResult.isCorrelated()) {
|
||||
return;
|
||||
}
|
||||
if (analyzedResult.hasGroupBy()) {
|
||||
throw new AnalysisException("Unsupported correlated subquery with grouping and/or aggregation "
|
||||
+ analyzedResult.getLogicalPlan());
|
||||
}
|
||||
}
|
||||
|
||||
private AnalyzedResult analyzeSubquery(SubqueryExpr expr) {
|
||||
CascadesContext subqueryContext = new Memo(expr.getQueryPlan())
|
||||
.newCascadesContext((cascadesContext.getStatementContext()), cascadesContext.getCteContext());
|
||||
Scope subqueryScope = genScopeWithSubquery(expr);
|
||||
subqueryContext
|
||||
.newAnalyzer(Optional.of(subqueryScope))
|
||||
.analyze();
|
||||
return new AnalyzedResult((LogicalPlan) subqueryContext.getMemo().copyOut(false),
|
||||
subqueryScope.getCorrelatedSlots());
|
||||
}
|
||||
|
||||
private Scope genScopeWithSubquery(SubqueryExpr expr) {
|
||||
return new Scope(getScope().getOuterScope(),
|
||||
getScope().getSlots(),
|
||||
Optional.ofNullable(expr));
|
||||
}
|
||||
|
||||
public Scope getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public CascadesContext getCascadesContext() {
|
||||
return cascadesContext;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AnalyzedResult {
|
||||
private final LogicalPlan logicalPlan;
|
||||
private final List<Slot> correlatedSlots;
|
||||
|
||||
public AnalyzedResult(LogicalPlan logicalPlan, List<Slot> correlatedSlots) {
|
||||
this.logicalPlan = Objects.requireNonNull(logicalPlan, "logicalPlan can not be null");
|
||||
this.correlatedSlots = correlatedSlots == null ? new ArrayList<>() : ImmutableList.copyOf(correlatedSlots);
|
||||
}
|
||||
|
||||
public LogicalPlan getLogicalPlan() {
|
||||
return logicalPlan;
|
||||
}
|
||||
|
||||
public List<Slot> getCorrelatedSlots() {
|
||||
return correlatedSlots;
|
||||
}
|
||||
|
||||
public boolean isCorrelated() {
|
||||
return !correlatedSlots.isEmpty();
|
||||
}
|
||||
|
||||
public boolean rootIsAgg() {
|
||||
return logicalPlan instanceof LogicalAggregate;
|
||||
}
|
||||
|
||||
public boolean hasGroupBy() {
|
||||
if (rootIsAgg()) {
|
||||
return !((LogicalAggregate<? extends Plan>) logicalPlan).getGroupByExpressions().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When there is a repeat operator in the query,
|
||||
* it will change the nullable information of the output column, which will be changed uniformly here.
|
||||
*
|
||||
* adjust the project with the children output
|
||||
*/
|
||||
private List<NamedExpression> adjustNullableForProjects(
|
||||
LogicalProject<GroupPlan> project, List<NamedExpression> projects) {
|
||||
Set<Slot> childrenOutput = project.children().stream()
|
||||
.map(Plan::getOutput)
|
||||
.flatMap(List::stream)
|
||||
.filter(ExpressionTrait::nullable)
|
||||
.collect(Collectors.toSet());
|
||||
return projects.stream()
|
||||
.map(e -> e.accept(RewriteNullableToTrue.INSTANCE, childrenOutput))
|
||||
.map(NamedExpression.class::cast)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* same as project, adjust the agg with the children output.
|
||||
*/
|
||||
private List<NamedExpression> adjustNullableForAgg(
|
||||
LogicalAggregate<GroupPlan> aggregate, List<NamedExpression> output) {
|
||||
Set<Slot> childrenOutput = aggregate.children().stream()
|
||||
.map(Plan::getOutput)
|
||||
.flatMap(List::stream)
|
||||
.filter(ExpressionTrait::nullable)
|
||||
.collect(Collectors.toSet());
|
||||
return output.stream()
|
||||
.map(e -> e.accept(RewriteNullableToTrue.INSTANCE, childrenOutput))
|
||||
.map(NamedExpression.class::cast)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
return (E) new Binder(toScope(boundedSlots), cascadesContext).bind(expr);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
// 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.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.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.planner.PlannerContext;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class Binder extends SubExprAnalyzer {
|
||||
|
||||
public Binder(Scope scope, CascadesContext cascadesContext) {
|
||||
super(scope, cascadesContext);
|
||||
}
|
||||
|
||||
public Expression bind(Expression expression) {
|
||||
return expression.accept(this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitUnboundAlias(UnboundAlias unboundAlias, PlannerContext context) {
|
||||
Expression child = unboundAlias.child().accept(this, context);
|
||||
if (unboundAlias.getAlias().isPresent()) {
|
||||
return new Alias(child, unboundAlias.getAlias().get());
|
||||
}
|
||||
if (child instanceof NamedExpression) {
|
||||
return new Alias(child, ((NamedExpression) child).getName());
|
||||
} else {
|
||||
// TODO: resolve aliases
|
||||
return new Alias(child, child.toSql());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slot visitUnboundSlot(UnboundSlot unboundSlot, PlannerContext 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 (!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().add(bounded.get(0));
|
||||
}
|
||||
return bounded.get(0);
|
||||
default:
|
||||
// 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, PlannerContext context) {
|
||||
List<String> qualifier = unboundStar.getQualifier();
|
||||
List<Slot> slots = getScope().getSlots()
|
||||
.stream()
|
||||
.filter(slot -> !(slot instanceof SlotReference) || ((SlotReference) slot).isVisible())
|
||||
.collect(Collectors.toList());
|
||||
switch (qualifier.size()) {
|
||||
case 0: // select *
|
||||
return new BoundStar(slots);
|
||||
case 1: // select table.*
|
||||
case 2: // select 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));
|
||||
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 qualifierStar.get(0).equalsIgnoreCase(boundSlotQualifier.get(0))
|
||||
&& qualifierStar.get(1).equalsIgnoreCase(boundSlotQualifier.get(1));
|
||||
default:
|
||||
throw new AnalysisException("Not supported qualifier: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
default:
|
||||
throw new AnalysisException("Not supported name: "
|
||||
+ StringUtils.join(qualifierStar, ".") + ".*");
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new BoundStar(slots);
|
||||
}
|
||||
|
||||
private List<Slot> bindSlot(UnboundSlot unboundSlot, List<Slot> boundSlots) {
|
||||
return boundSlots.stream().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 qualifierDbName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
return qualifierDbName.equalsIgnoreCase(nameParts.get(0))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(1));
|
||||
} else if (nameParts.size() == 3) {
|
||||
String qualifierDbName = boundSlot.getQualifier().get(qualifierSize - 1);
|
||||
String qualifierClusterName = boundSlot.getQualifier().get(qualifierSize - 2);
|
||||
return qualifierClusterName.equalsIgnoreCase(nameParts.get(0))
|
||||
&& qualifierDbName.equalsIgnoreCase(nameParts.get(1))
|
||||
&& boundSlot.getName().equalsIgnoreCase(nameParts.get(2));
|
||||
}
|
||||
//TODO: handle name parts more than three.
|
||||
throw new AnalysisException("Not supported name: "
|
||||
+ StringUtils.join(nameParts, "."));
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
package org.apache.doris.nereids.rules.analysis;
|
||||
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.CTEContext;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.memo.Memo;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
|
||||
@ -0,0 +1,188 @@
|
||||
// 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.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.analyzer.Scope;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.memo.Memo;
|
||||
import org.apache.doris.nereids.trees.expressions.Exists;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.InSubquery;
|
||||
import org.apache.doris.nereids.trees.expressions.ListQuery;
|
||||
import org.apache.doris.nereids.trees.expressions.Not;
|
||||
import org.apache.doris.nereids.trees.expressions.ScalarSubquery;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.expressions.SubqueryExpr;
|
||||
import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.planner.PlannerContext;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Use the visitor to iterate sub expression.
|
||||
*/
|
||||
public class SubExprAnalyzer extends DefaultExpressionRewriter<PlannerContext> {
|
||||
|
||||
private final Scope scope;
|
||||
private final CascadesContext cascadesContext;
|
||||
|
||||
public SubExprAnalyzer(Scope scope, CascadesContext cascadesContext) {
|
||||
this.scope = scope;
|
||||
this.cascadesContext = cascadesContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitNot(Not not, PlannerContext context) {
|
||||
Expression child = not.child();
|
||||
if (child instanceof Exists) {
|
||||
return visitExistsSubquery(
|
||||
new Exists(((Exists) child).getQueryPlan(), true), context);
|
||||
} else if (child instanceof InSubquery) {
|
||||
return visitInSubquery(new InSubquery(((InSubquery) child).getCompareExpr(),
|
||||
((InSubquery) child).getListQuery(), true), context);
|
||||
}
|
||||
return visit(not, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitExistsSubquery(Exists exists, PlannerContext context) {
|
||||
AnalyzedResult analyzedResult = analyzeSubquery(exists);
|
||||
|
||||
return new Exists(analyzedResult.getLogicalPlan(),
|
||||
analyzedResult.getCorrelatedSlots(), exists.isNot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitInSubquery(InSubquery expr, PlannerContext context) {
|
||||
AnalyzedResult analyzedResult = analyzeSubquery(expr);
|
||||
|
||||
checkOutputColumn(analyzedResult.getLogicalPlan());
|
||||
checkHasGroupBy(analyzedResult);
|
||||
|
||||
return new InSubquery(
|
||||
expr.getCompareExpr().accept(this, context),
|
||||
new ListQuery(analyzedResult.getLogicalPlan()),
|
||||
analyzedResult.getCorrelatedSlots(), expr.isNot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression visitScalarSubquery(ScalarSubquery scalar, PlannerContext context) {
|
||||
AnalyzedResult analyzedResult = analyzeSubquery(scalar);
|
||||
|
||||
checkOutputColumn(analyzedResult.getLogicalPlan());
|
||||
checkRootIsAgg(analyzedResult);
|
||||
checkHasGroupBy(analyzedResult);
|
||||
|
||||
return new ScalarSubquery(analyzedResult.getLogicalPlan(), analyzedResult.getCorrelatedSlots());
|
||||
}
|
||||
|
||||
private void checkOutputColumn(LogicalPlan plan) {
|
||||
if (plan.getOutput().size() != 1) {
|
||||
throw new AnalysisException("Multiple columns returned by subquery are not yet supported. Found "
|
||||
+ plan.getOutput().size());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRootIsAgg(AnalyzedResult analyzedResult) {
|
||||
if (!analyzedResult.isCorrelated()) {
|
||||
return;
|
||||
}
|
||||
if (!analyzedResult.rootIsAgg()) {
|
||||
throw new AnalysisException("The select item in correlated subquery of binary predicate "
|
||||
+ "should only be sum, min, max, avg and count. Current subquery: "
|
||||
+ analyzedResult.getLogicalPlan());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkHasGroupBy(AnalyzedResult analyzedResult) {
|
||||
if (!analyzedResult.isCorrelated()) {
|
||||
return;
|
||||
}
|
||||
if (analyzedResult.hasGroupBy()) {
|
||||
throw new AnalysisException("Unsupported correlated subquery with grouping and/or aggregation "
|
||||
+ analyzedResult.getLogicalPlan());
|
||||
}
|
||||
}
|
||||
|
||||
private AnalyzedResult analyzeSubquery(SubqueryExpr expr) {
|
||||
CascadesContext subqueryContext = new Memo(expr.getQueryPlan())
|
||||
.newCascadesContext((cascadesContext.getStatementContext()), cascadesContext.getCteContext());
|
||||
Scope subqueryScope = genScopeWithSubquery(expr);
|
||||
subqueryContext
|
||||
.newAnalyzer(Optional.of(subqueryScope))
|
||||
.analyze();
|
||||
return new AnalyzedResult((LogicalPlan) subqueryContext.getMemo().copyOut(false),
|
||||
subqueryScope.getCorrelatedSlots());
|
||||
}
|
||||
|
||||
private Scope genScopeWithSubquery(SubqueryExpr expr) {
|
||||
return new Scope(getScope().getOuterScope(),
|
||||
getScope().getSlots(),
|
||||
Optional.ofNullable(expr));
|
||||
}
|
||||
|
||||
public Scope getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public CascadesContext getCascadesContext() {
|
||||
return cascadesContext;
|
||||
}
|
||||
|
||||
private static class AnalyzedResult {
|
||||
private final LogicalPlan logicalPlan;
|
||||
private final List<Slot> correlatedSlots;
|
||||
|
||||
public AnalyzedResult(LogicalPlan logicalPlan, List<Slot> correlatedSlots) {
|
||||
this.logicalPlan = Objects.requireNonNull(logicalPlan, "logicalPlan can not be null");
|
||||
this.correlatedSlots = correlatedSlots == null ? new ArrayList<>() : ImmutableList.copyOf(correlatedSlots);
|
||||
}
|
||||
|
||||
public LogicalPlan getLogicalPlan() {
|
||||
return logicalPlan;
|
||||
}
|
||||
|
||||
public List<Slot> getCorrelatedSlots() {
|
||||
return correlatedSlots;
|
||||
}
|
||||
|
||||
public boolean isCorrelated() {
|
||||
return !correlatedSlots.isEmpty();
|
||||
}
|
||||
|
||||
public boolean rootIsAgg() {
|
||||
return logicalPlan instanceof LogicalAggregate;
|
||||
}
|
||||
|
||||
public boolean hasGroupBy() {
|
||||
if (rootIsAgg()) {
|
||||
return !((LogicalAggregate<? extends Plan>) logicalPlan).getGroupByExpressions().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
// 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.trees.expressions;
|
||||
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
|
||||
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** BoundStar is used to wrap list of slots for temporary. */
|
||||
public class BoundStar extends NamedExpression implements PropagateNullable {
|
||||
public BoundStar(List<Slot> children) {
|
||||
super(children.toArray(new Slot[0]));
|
||||
Preconditions.checkArgument(children.stream().noneMatch(slot -> slot instanceof UnboundSlot),
|
||||
"BoundStar can not wrap UnboundSlot"
|
||||
);
|
||||
}
|
||||
|
||||
public String toSql() {
|
||||
return children.stream().map(Expression::toSql).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public List<Slot> getSlots() {
|
||||
return (List) children();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
|
||||
return visitor.visitBoundStar(this, context);
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,7 @@ public abstract class SubqueryExpr extends Expression {
|
||||
|
||||
@Override
|
||||
public DataType getDataType() throws UnboundException {
|
||||
throw new UnboundException("not support");
|
||||
throw new UnboundException("getDataType");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -21,7 +21,6 @@ import org.apache.doris.nereids.analyzer.UnboundAlias;
|
||||
import org.apache.doris.nereids.analyzer.UnboundFunction;
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.analyzer.UnboundStar;
|
||||
import org.apache.doris.nereids.rules.analysis.BindSlotReference.BoundStar;
|
||||
import org.apache.doris.nereids.trees.expressions.Add;
|
||||
import org.apache.doris.nereids.trees.expressions.AggregateExpression;
|
||||
import org.apache.doris.nereids.trees.expressions.Alias;
|
||||
@ -34,6 +33,7 @@ import org.apache.doris.nereids.trees.expressions.BitAnd;
|
||||
import org.apache.doris.nereids.trees.expressions.BitNot;
|
||||
import org.apache.doris.nereids.trees.expressions.BitOr;
|
||||
import org.apache.doris.nereids.trees.expressions.BitXor;
|
||||
import org.apache.doris.nereids.trees.expressions.BoundStar;
|
||||
import org.apache.doris.nereids.trees.expressions.CaseWhen;
|
||||
import org.apache.doris.nereids.trees.expressions.Cast;
|
||||
import org.apache.doris.nereids.trees.expressions.ComparisonPredicate;
|
||||
|
||||
@ -38,7 +38,6 @@ public enum JoinType {
|
||||
RIGHT_ANTI_JOIN,
|
||||
CROSS_JOIN,
|
||||
NULL_AWARE_LEFT_ANTI_JOIN,
|
||||
USING_JOIN,
|
||||
;
|
||||
|
||||
private static final Map<JoinType, JoinType> joinSwapMap = ImmutableMap
|
||||
|
||||
@ -19,7 +19,6 @@ package org.apache.doris.nereids.trees.plans.algebra;
|
||||
|
||||
import org.apache.doris.catalog.Database;
|
||||
import org.apache.doris.catalog.Table;
|
||||
import org.apache.doris.nereids.analyzer.Relation;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
|
||||
/** CatalogRelation */
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.doris.nereids.trees.plans.algebra;
|
||||
|
||||
import org.apache.doris.nereids.analyzer.Relation;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.doris.nereids.trees.plans.algebra;
|
||||
|
||||
import org.apache.doris.nereids.analyzer.Relation;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.analyzer;
|
||||
package org.apache.doris.nereids.trees.plans.algebra;
|
||||
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
package org.apache.doris.nereids.trees.plans.algebra;
|
||||
|
||||
import org.apache.doris.catalog.TableIf;
|
||||
import org.apache.doris.nereids.analyzer.Relation;
|
||||
|
||||
/**
|
||||
* Common interface for logical/physical scan.
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.doris.nereids.trees.plans.algebra;
|
||||
|
||||
import org.apache.doris.nereids.analyzer.Relation;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.table.TableValuedFunction;
|
||||
|
||||
/** TVFRelation */
|
||||
|
||||
@ -20,7 +20,7 @@ package org.apache.doris.nereids.trees.plans.logical;
|
||||
import org.apache.doris.nereids.analyzer.UnboundStar;
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.properties.LogicalProperties;
|
||||
import org.apache.doris.nereids.rules.analysis.BindSlotReference.BoundStar;
|
||||
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;
|
||||
|
||||
@ -173,7 +173,7 @@ public class NereidsParserTest extends ParserTestBase {
|
||||
String innerJoin2 = "SELECT t1.a FROM t1 JOIN t2 ON t1.id = t2.id;";
|
||||
logicalPlan = nereidsParser.parseSingle(innerJoin2);
|
||||
logicalJoin = (LogicalJoin) logicalPlan.child(0);
|
||||
Assertions.assertEquals(JoinType.CROSS_JOIN, logicalJoin.getJoinType());
|
||||
Assertions.assertEquals(JoinType.INNER_JOIN, logicalJoin.getJoinType());
|
||||
|
||||
String leftJoin1 = "SELECT t1.a FROM t1 LEFT JOIN t2 ON t1.id = t2.id;";
|
||||
logicalPlan = nereidsParser.parseSingle(leftJoin1);
|
||||
|
||||
@ -131,7 +131,7 @@ public class AnalyzeSubQueryTest extends TestWithFeService implements PatternMat
|
||||
.applyTopDown(new LogicalSubQueryAliasToLogicalProject())
|
||||
.matchesFromRoot(
|
||||
logicalProject(
|
||||
crossLogicalJoin(
|
||||
innerLogicalJoin(
|
||||
logicalProject(
|
||||
logicalOlapScan()
|
||||
),
|
||||
@ -167,7 +167,7 @@ public class AnalyzeSubQueryTest extends TestWithFeService implements PatternMat
|
||||
.applyTopDown(new LogicalSubQueryAliasToLogicalProject())
|
||||
.matchesFromRoot(
|
||||
logicalProject(
|
||||
crossLogicalJoin(
|
||||
innerLogicalJoin(
|
||||
logicalOlapScan(),
|
||||
logicalProject(
|
||||
logicalOlapScan()
|
||||
|
||||
@ -20,6 +20,7 @@ package org.apache.doris.nereids.rules.analysis;
|
||||
import org.apache.doris.common.NereidsException;
|
||||
import org.apache.doris.nereids.NereidsPlanner;
|
||||
import org.apache.doris.nereids.StatementContext;
|
||||
import org.apache.doris.nereids.analyzer.CTEContext;
|
||||
import org.apache.doris.nereids.datasets.ssb.SSBUtils;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.glue.translator.PhysicalPlanTranslator;
|
||||
@ -236,7 +237,7 @@ public class RegisterCTETest extends TestWithFeService implements PatternMatchSu
|
||||
.analyze(sql4)
|
||||
.matchesFromRoot(
|
||||
logicalProject(
|
||||
crossLogicalJoin(
|
||||
innerLogicalJoin(
|
||||
logicalSubQueryAlias(
|
||||
logicalProject().when(p -> p.getProjects().equals(ImmutableList.of(skAlias)))
|
||||
).when(a -> a.getAlias().equals("cte1")),
|
||||
|
||||
@ -19,13 +19,13 @@ billie eillish
|
||||
a-b-c
|
||||
|
||||
-- !sql --
|
||||
a <\\1> c
|
||||
a <b> c
|
||||
|
||||
-- !sql --
|
||||
a-b c
|
||||
|
||||
-- !sql --
|
||||
a <\\1> b
|
||||
a <b> b
|
||||
|
||||
-- !sql --
|
||||
false 1 1989 1001 11011902 123.123 true 1989-03-21 1989-03-21T13:00 wangjuoo4 0.1 6.333 string12345 170141183460469231731687303715884105727
|
||||
|
||||
Reference in New Issue
Block a user