[Feature](nereids) support sub-query and alias in FromClause (#11035)
Support sub-query and alias for TPC-H,for example: select * from (select * from (T1) A join T2 as B on T1.id = T2.id) T;
This commit is contained in:
@ -26,6 +26,7 @@ import org.apache.doris.nereids.glue.LogicalPlanAdapter;
|
||||
import org.apache.doris.nereids.glue.translator.PhysicalPlanTranslator;
|
||||
import org.apache.doris.nereids.glue.translator.PlanTranslatorContext;
|
||||
import org.apache.doris.nereids.jobs.batch.DisassembleRulesJob;
|
||||
import org.apache.doris.nereids.jobs.batch.FinalizeAnalyzeJob;
|
||||
import org.apache.doris.nereids.jobs.batch.JoinReorderRulesJob;
|
||||
import org.apache.doris.nereids.jobs.batch.NormalizeExpressionRulesJob;
|
||||
import org.apache.doris.nereids.jobs.batch.OptimizeRulesJob;
|
||||
@ -106,6 +107,7 @@ public class NereidsPlanner extends Planner {
|
||||
// cascades style optimize phase.
|
||||
.setJobContext(outputProperties);
|
||||
|
||||
finalizeAnalyze();
|
||||
rewrite();
|
||||
// TODO: remove this condition, when stats collector is fully developed.
|
||||
if (ConnectContext.get().getSessionVariable().isEnableNereidsCBO()) {
|
||||
@ -116,6 +118,10 @@ public class NereidsPlanner extends Planner {
|
||||
return getRoot().extractPlan();
|
||||
}
|
||||
|
||||
private void finalizeAnalyze() {
|
||||
new FinalizeAnalyzeJob(plannerContext).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical plan rewrite based on a series of heuristic rules.
|
||||
*/
|
||||
|
||||
@ -402,6 +402,7 @@ public class PhysicalPlanTranslator extends DefaultPlanVisitor<PlanFragment, Pla
|
||||
.stream()
|
||||
.map(e -> ExpressionTranslator.translate(e, context))
|
||||
.collect(Collectors.toList());
|
||||
// TODO: fix the project alias of an aliased relation.
|
||||
PlanNode inputPlanNode = inputFragment.getPlanRoot();
|
||||
List<Expr> predicateList = inputPlanNode.getConjuncts();
|
||||
Set<Integer> requiredSlotIdList = new HashSet<>();
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
// 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.jobs.batch;
|
||||
|
||||
import org.apache.doris.nereids.PlannerContext;
|
||||
import org.apache.doris.nereids.rules.analysis.EliminateAliasNode;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Job to eliminate the logical node of sub query and alias
|
||||
*/
|
||||
public class FinalizeAnalyzeJob extends BatchRulesJob {
|
||||
|
||||
/**
|
||||
* constructor
|
||||
* @param plannerContext ctx
|
||||
*/
|
||||
public FinalizeAnalyzeJob(PlannerContext plannerContext) {
|
||||
super(plannerContext);
|
||||
rulesJob.addAll(ImmutableList.of(
|
||||
bottomUpBatch(ImmutableList.of(new EliminateAliasNode()))
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -63,6 +63,5 @@ public class OptimizeGroupExpressionJob extends Job {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@ import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalRelation;
|
||||
import org.apache.doris.nereids.trees.plans.physical.PhysicalRelation;
|
||||
import org.apache.doris.statistics.StatsDeriveResult;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
@ -168,7 +170,13 @@ public class GroupExpression {
|
||||
return false;
|
||||
}
|
||||
GroupExpression that = (GroupExpression) o;
|
||||
return plan.equals(that.plan) && children.equals(that.children);
|
||||
// if the plan is LogicalRelation or PhysicalRelation, this == that should be true,
|
||||
// when if one relation appear in plan more than once,
|
||||
// we cannot distinguish them throw equals function, since equals function cannot use output info.
|
||||
if (plan instanceof LogicalRelation || plan instanceof PhysicalRelation) {
|
||||
return false;
|
||||
}
|
||||
return children.equals(that.children) && plan.equals(that.plan);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -21,6 +21,8 @@ package org.apache.doris.nereids.parser;
|
||||
import org.apache.doris.analysis.ArithmeticExpr.Operator;
|
||||
import org.apache.doris.nereids.DorisParser;
|
||||
import org.apache.doris.nereids.DorisParser.AggClauseContext;
|
||||
import org.apache.doris.nereids.DorisParser.AliasedQueryContext;
|
||||
import org.apache.doris.nereids.DorisParser.AliasedRelationContext;
|
||||
import org.apache.doris.nereids.DorisParser.ArithmeticBinaryContext;
|
||||
import org.apache.doris.nereids.DorisParser.ArithmeticUnaryContext;
|
||||
import org.apache.doris.nereids.DorisParser.BooleanLiteralContext;
|
||||
@ -59,6 +61,7 @@ import org.apache.doris.nereids.DorisParser.SortItemContext;
|
||||
import org.apache.doris.nereids.DorisParser.StarContext;
|
||||
import org.apache.doris.nereids.DorisParser.StringLiteralContext;
|
||||
import org.apache.doris.nereids.DorisParser.SubqueryExpressionContext;
|
||||
import org.apache.doris.nereids.DorisParser.TableAliasContext;
|
||||
import org.apache.doris.nereids.DorisParser.TableNameContext;
|
||||
import org.apache.doris.nereids.DorisParser.TypeConstructorContext;
|
||||
import org.apache.doris.nereids.DorisParser.UnitIdentifierContext;
|
||||
@ -69,6 +72,8 @@ import org.apache.doris.nereids.analyzer.UnboundFunction;
|
||||
import org.apache.doris.nereids.analyzer.UnboundRelation;
|
||||
import org.apache.doris.nereids.analyzer.UnboundSlot;
|
||||
import org.apache.doris.nereids.analyzer.UnboundStar;
|
||||
import org.apache.doris.nereids.annotation.Developing;
|
||||
import org.apache.doris.nereids.exceptions.ParseException;
|
||||
import org.apache.doris.nereids.properties.OrderKey;
|
||||
import org.apache.doris.nereids.trees.expressions.Add;
|
||||
import org.apache.doris.nereids.trees.expressions.Alias;
|
||||
@ -118,6 +123,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
@ -212,11 +218,33 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
/**
|
||||
* Create an aliased table reference. This is typically used in FROM clauses.
|
||||
*/
|
||||
@Developing
|
||||
private LogicalPlan withTableAlias(LogicalPlan plan, TableAliasContext ctx) {
|
||||
String alias = ctx.strictIdentifier().getText();
|
||||
if (null != ctx.identifierList()) {
|
||||
throw new ParseException("Do not implemented", ctx);
|
||||
// TODO: multi-colName
|
||||
}
|
||||
return new LogicalSubQueryAlias<>(alias, plan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogicalPlan visitTableName(TableNameContext ctx) {
|
||||
List<String> tableId = visitMultipartIdentifier(ctx.multipartIdentifier());
|
||||
// TODO: sample and time travel, alias, sub query
|
||||
return new UnboundRelation(tableId);
|
||||
if (null == ctx.tableAlias().strictIdentifier()) {
|
||||
return new UnboundRelation(tableId);
|
||||
}
|
||||
return withTableAlias(new UnboundRelation(tableId), ctx.tableAlias());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogicalPlan visitAliasedQuery(AliasedQueryContext ctx) {
|
||||
return withTableAlias(visitQuery(ctx.query()), ctx.tableAlias());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogicalPlan visitAliasedRelation(AliasedRelationContext ctx) {
|
||||
return withTableAlias(visitRelation(ctx.relation()), ctx.tableAlias());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -568,19 +596,33 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
return visit(namedCtx.namedExpression(), Expression.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogicalPlan visitRelation(RelationContext ctx) {
|
||||
LogicalPlan right = plan(ctx.relationPrimary());
|
||||
if (ctx.LATERAL() != null) {
|
||||
if (!(right instanceof LogicalSubQueryAlias)) {
|
||||
throw new IllegalStateException("lateral join right table should be sub-query");
|
||||
}
|
||||
}
|
||||
return right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogicalPlan visitFromClause(FromClauseContext ctx) {
|
||||
return ParserUtils.withOrigin(ctx, () -> {
|
||||
LogicalPlan left = null;
|
||||
// build left deep join tree
|
||||
for (RelationContext relation : ctx.relation()) {
|
||||
LogicalPlan right = plan(relation.relationPrimary());
|
||||
left = (left == null)
|
||||
? right
|
||||
: new LogicalJoin<>(JoinType.CROSS_JOIN, Optional.empty(), left, right);
|
||||
// build left deep join tree
|
||||
LogicalPlan right = visitRelation(relation);
|
||||
left = (left == null) ? right :
|
||||
new LogicalJoin<>(
|
||||
JoinType.CROSS_JOIN,
|
||||
Optional.empty(),
|
||||
left,
|
||||
right);
|
||||
left = withJoinRelations(left, relation);
|
||||
// TODO: pivot and lateral view
|
||||
}
|
||||
// TODO: pivot and lateral view
|
||||
return left;
|
||||
});
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ public class OrderKey {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return expr.toSql();
|
||||
return expr.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -33,6 +33,7 @@ public enum RuleType {
|
||||
BINDING_SORT_SLOT(RuleTypeClass.REWRITE),
|
||||
BINDING_PROJECT_FUNCTION(RuleTypeClass.REWRITE),
|
||||
BINDING_AGGREGATE_FUNCTION(RuleTypeClass.REWRITE),
|
||||
BINDING_SUBQUERY_ALIAS_SLOT(RuleTypeClass.REWRITE),
|
||||
BINDING_FILTER_FUNCTION(RuleTypeClass.REWRITE),
|
||||
|
||||
RESOLVE_PROJECT_ALIAS(RuleTypeClass.REWRITE),
|
||||
@ -41,6 +42,14 @@ public enum RuleType {
|
||||
// rewrite rules
|
||||
AGGREGATE_DISASSEMBLE(RuleTypeClass.REWRITE),
|
||||
COLUMN_PRUNE_PROJECTION(RuleTypeClass.REWRITE),
|
||||
ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
|
||||
PROJECT_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
FILTER_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
JOIN_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
JOIN_LEFT_CHILD_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
JOIN_RIGHT_CHILD_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
AGGREGATE_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
|
||||
// predicate push down rules
|
||||
PUSH_DOWN_PREDICATE_THROUGH_JOIN(RuleTypeClass.REWRITE),
|
||||
PUSH_DOWN_PREDICATE_THROUGH_AGGREGATION(RuleTypeClass.REWRITE),
|
||||
|
||||
@ -20,9 +20,13 @@ package org.apache.doris.nereids.rules.analysis;
|
||||
import org.apache.doris.catalog.Database;
|
||||
import org.apache.doris.catalog.Env;
|
||||
import org.apache.doris.catalog.Table;
|
||||
import org.apache.doris.catalog.TableIf.TableType;
|
||||
import org.apache.doris.nereids.analyzer.NereidsAnalyzer;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
|
||||
import org.apache.doris.qe.ConnectContext;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -41,16 +45,11 @@ public class BindRelation extends OneAnalysisRuleFactory {
|
||||
switch (nameParts.size()) {
|
||||
case 1: {
|
||||
// Use current database name from catalog.
|
||||
String dbName = connectContext.getDatabase();
|
||||
Table table = getTable(dbName, nameParts.get(0), connectContext.getEnv());
|
||||
// TODO: should generate different Scan sub class according to table's type
|
||||
return new LogicalOlapScan(table, ImmutableList.of(dbName));
|
||||
return bindWithCurrentDb(connectContext, nameParts);
|
||||
}
|
||||
case 2: {
|
||||
// Use database name from table name parts.
|
||||
String dbName = connectContext.getClusterName() + ":" + nameParts.get(0);
|
||||
Table table = getTable(dbName, nameParts.get(1), connectContext.getEnv());
|
||||
return new LogicalOlapScan(table, ImmutableList.of(dbName));
|
||||
return bindWithDbNameFromNamePart(connectContext, nameParts);
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Table name [" + ctx.root.getTableName() + "] is invalid.");
|
||||
@ -69,4 +68,33 @@ public class BindRelation extends OneAnalysisRuleFactory {
|
||||
db.readUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
private LogicalPlan bindWithCurrentDb(ConnectContext ctx, List<String> nameParts) {
|
||||
String dbName = ctx.getDatabase();
|
||||
Table table = getTable(dbName, nameParts.get(0), ctx.getEnv());
|
||||
// TODO: should generate different Scan sub class according to table's type
|
||||
if (table.getType() == TableType.OLAP) {
|
||||
return new LogicalOlapScan(table, ImmutableList.of(dbName));
|
||||
} else if (table.getType() == TableType.VIEW) {
|
||||
LogicalPlan viewPlan = new NereidsAnalyzer(ctx).analyze(table.getDdlSql());
|
||||
return new LogicalSubQueryAlias<>(table.getName(), viewPlan);
|
||||
}
|
||||
throw new RuntimeException("Unsupported tableType:" + table.getType());
|
||||
}
|
||||
|
||||
private LogicalPlan bindWithDbNameFromNamePart(ConnectContext ctx, List<String> nameParts) {
|
||||
// if the relation is view, nameParts.get(0) is dbName.
|
||||
String dbName = nameParts.get(0);
|
||||
if (!dbName.equals(ctx.getDatabase())) {
|
||||
dbName = ctx.getClusterName() + ":" + nameParts.get(0);
|
||||
}
|
||||
Table table = getTable(dbName, nameParts.get(1), ctx.getEnv());
|
||||
if (table.getType() == TableType.OLAP) {
|
||||
return new LogicalOlapScan(table, ImmutableList.of(dbName));
|
||||
} else if (table.getType() == TableType.VIEW) {
|
||||
LogicalPlan viewPlan = new NereidsAnalyzer(ctx).analyze(table.getDdlSql());
|
||||
return new LogicalSubQueryAlias<>(table.getName(), viewPlan);
|
||||
}
|
||||
throw new RuntimeException("Unsupported tableType:" + table.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -103,6 +104,9 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
|
||||
return new LogicalSort<>(sortItemList, sort.child());
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_SUBQUERY_ALIAS_SLOT.build(
|
||||
logicalSubQueryAlias().then(alias -> new LogicalSubQueryAlias<>(alias.getAlias(), alias.child()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
// 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.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Eliminate the logical sub query and alias node after analyze and before rewrite
|
||||
* If we match the alias node and return its child node, in the execute() of the job
|
||||
* <p>
|
||||
* TODO: refactor group merge strategy to support the feature above
|
||||
*/
|
||||
public class EliminateAliasNode implements AnalysisRuleFactory {
|
||||
@Override
|
||||
public List<Rule> buildRules() {
|
||||
return ImmutableList.of(
|
||||
RuleType.PROJECT_ELIMINATE_ALIAS_NODE.build(
|
||||
logicalProject(logicalSubQueryAlias())
|
||||
.then(project -> project.withChildren(ImmutableList.of(project.child().child())))
|
||||
),
|
||||
RuleType.FILTER_ELIMINATE_ALIAS_NODE.build(
|
||||
logicalFilter(logicalSubQueryAlias())
|
||||
.then(filter -> filter.withChildren(ImmutableList.of(filter.child().child())))
|
||||
),
|
||||
RuleType.AGGREGATE_ELIMINATE_ALIAS_NODE.build(
|
||||
logicalAggregate(logicalSubQueryAlias())
|
||||
.then(agg -> agg.withChildren(ImmutableList.of(agg.child().child())))
|
||||
),
|
||||
RuleType.JOIN_ELIMINATE_ALIAS_NODE.build(
|
||||
logicalJoin(logicalSubQueryAlias(), logicalSubQueryAlias())
|
||||
.then(join -> join.withChildren(
|
||||
ImmutableList.of(join.left().child(), join.right().child())))
|
||||
),
|
||||
RuleType.JOIN_LEFT_CHILD_ELIMINATE_ALIAS_NODE.build(
|
||||
logicalJoin(logicalSubQueryAlias(), group())
|
||||
.then(join -> join.withChildren(
|
||||
ImmutableList.of(join.left().child(), join.right())))
|
||||
),
|
||||
RuleType.JOIN_RIGHT_CHILD_ELIMINATE_ALIAS_NODE.build(
|
||||
logicalJoin(group(), logicalSubQueryAlias())
|
||||
.then(join -> join.withChildren(
|
||||
ImmutableList.of(join.left(), join.right().child())))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,11 @@ package org.apache.doris.nereids.trees.expressions;
|
||||
|
||||
import org.apache.doris.common.IdGenerator;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* The util of named expression.
|
||||
*/
|
||||
@ -31,4 +36,17 @@ public class NamedExpressionUtil {
|
||||
public static ExprId newExprId() {
|
||||
return ID_GENERATOR.getNextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Id Generator
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void clear() throws Exception {
|
||||
Field f = NamedExpressionUtil.class.getDeclaredField("ID_GENERATOR");
|
||||
f.setAccessible(true);
|
||||
Field mf = Field.class.getDeclaredField("modifiers");
|
||||
mf.setAccessible(true);
|
||||
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
||||
f.set(NamedExpressionUtil.class, ExprId.createGenerator());
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package org.apache.doris.nereids.trees.expressions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract class for all slot in expression.
|
||||
*/
|
||||
@ -30,4 +32,8 @@ public abstract class Slot extends NamedExpression implements LeafExpression {
|
||||
public Slot withNullable(boolean newNullable) {
|
||||
throw new RuntimeException("Do not implement");
|
||||
}
|
||||
|
||||
public Slot withQualifier(List<String> qualifiers) {
|
||||
throw new RuntimeException("Do not implement");
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,4 +149,9 @@ public class SlotReference extends Slot {
|
||||
}
|
||||
return new SlotReference(exprId, name, dataType, newNullable, qualifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slot withQualifier(List<String> qualifiers) {
|
||||
return new SlotReference(exprId, name, dataType, nullable, qualifiers);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ public enum PlanType {
|
||||
UNKNOWN,
|
||||
|
||||
// logical plan
|
||||
LOGICAL_SUBQUERY_ALIAS,
|
||||
LOGICAL_UNBOUND_RELATION,
|
||||
LOGICAL_BOUND_RELATION,
|
||||
LOGICAL_PROJECT,
|
||||
|
||||
@ -62,6 +62,7 @@ public class LogicalProject<CHILD_TYPE extends Plan> extends LogicalUnary<CHILD_
|
||||
*
|
||||
* @return all project of this node.
|
||||
*/
|
||||
@Override
|
||||
public List<NamedExpression> getProjects() {
|
||||
return projects;
|
||||
}
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
// 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.plans.logical;
|
||||
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.properties.LogicalProperties;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.PlanType;
|
||||
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The node of logical plan for sub query and alias
|
||||
*
|
||||
* @param <CHILD_TYPE> param
|
||||
*/
|
||||
public class LogicalSubQueryAlias<CHILD_TYPE extends Plan> extends LogicalUnary<CHILD_TYPE> {
|
||||
private final String alias;
|
||||
|
||||
public LogicalSubQueryAlias(String tableAlias, CHILD_TYPE child) {
|
||||
this(tableAlias, Optional.empty(), Optional.empty(), child);
|
||||
}
|
||||
|
||||
public LogicalSubQueryAlias(String tableAlias, Optional<GroupExpression> groupExpression,
|
||||
Optional<LogicalProperties> logicalProperties, CHILD_TYPE child) {
|
||||
super(PlanType.LOGICAL_SUBQUERY_ALIAS, groupExpression, logicalProperties, child);
|
||||
this.alias = tableAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> computeOutput(Plan input) {
|
||||
return input.getOutput().stream()
|
||||
.map(slot -> slot.withQualifier(ImmutableList.of(alias)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogicalSubQueryAlias (" + alias + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LogicalSubQueryAlias that = (LogicalSubQueryAlias) o;
|
||||
return alias.equals(that.alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan withChildren(List<Plan> children) {
|
||||
Preconditions.checkArgument(children.size() == 1);
|
||||
return new LogicalSubQueryAlias<>(alias, children.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
|
||||
return visitor.visitSubQueryAlias((LogicalSubQueryAlias<Plan>) this, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Expression> getExpressions() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan withGroupExpression(Optional<GroupExpression> groupExpression) {
|
||||
return new LogicalSubQueryAlias<>(alias, groupExpression, Optional.of(logicalProperties), child());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan withLogicalProperties(Optional<LogicalProperties> logicalProperties) {
|
||||
return new LogicalSubQueryAlias<>(alias, Optional.empty(), logicalProperties, child());
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalRelation;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
|
||||
import org.apache.doris.nereids.trees.plans.physical.PhysicalAggregate;
|
||||
import org.apache.doris.nereids.trees.plans.physical.PhysicalDistribution;
|
||||
import org.apache.doris.nereids.trees.plans.physical.PhysicalFilter;
|
||||
@ -56,6 +57,10 @@ public abstract class PlanVisitor<R, C> {
|
||||
// Logical plans
|
||||
// *******************************
|
||||
|
||||
public R visitSubQueryAlias(LogicalSubQueryAlias<Plan> alias, C context) {
|
||||
return visit(alias, context);
|
||||
}
|
||||
|
||||
public R visitUnboundRelation(UnboundRelation relation, C context) {
|
||||
return visit(relation, context);
|
||||
}
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
// 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.util;
|
||||
|
||||
import org.apache.doris.nereids.NereidsPlanner;
|
||||
import org.apache.doris.nereids.analyzer.NereidsAnalyzer;
|
||||
import org.apache.doris.nereids.glue.translator.PhysicalPlanTranslator;
|
||||
import org.apache.doris.nereids.glue.translator.PlanTranslatorContext;
|
||||
import org.apache.doris.nereids.parser.NereidsParser;
|
||||
import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.rules.analysis.EliminateAliasNode;
|
||||
import org.apache.doris.nereids.trees.expressions.EqualTo;
|
||||
import org.apache.doris.nereids.trees.expressions.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpressionUtil;
|
||||
import org.apache.doris.nereids.trees.expressions.SlotReference;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
|
||||
import org.apache.doris.nereids.types.BigIntType;
|
||||
import org.apache.doris.utframe.TestWithFeService;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AnalyzeSubQueryTest extends TestWithFeService implements PatternMatchSupported {
|
||||
private final NereidsParser parser = new NereidsParser();
|
||||
|
||||
private final List<String> testSql = ImmutableList.of(
|
||||
"SELECT * FROM (SELECT * FROM T1 T) T2",
|
||||
"SELECT * FROM T1 TT1 JOIN (SELECT * FROM T2 TT2) T ON TT1.ID = T.ID",
|
||||
"SELECT * FROM T1 TT1 JOIN (SELECT TT2.ID FROM T2 TT2) T ON TT1.ID = T.ID",
|
||||
"SELECT T.ID FROM T1 T",
|
||||
"SELECT A.ID FROM T1 A, T2 B WHERE A.ID = B.ID",
|
||||
"SELECT * FROM T1 JOIN T1 T2 ON T1.ID = T2.ID"
|
||||
);
|
||||
|
||||
@Override
|
||||
protected void runBeforeAll() throws Exception {
|
||||
createDatabase("test");
|
||||
connectContext.setDatabase("default_cluster:test");
|
||||
|
||||
createTables(
|
||||
"CREATE TABLE IF NOT EXISTS T1 (\n"
|
||||
+ " id bigint,\n"
|
||||
+ " score bigint\n"
|
||||
+ ")\n"
|
||||
+ "DUPLICATE KEY(id)\n"
|
||||
+ "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
|
||||
+ "PROPERTIES (\n"
|
||||
+ " \"replication_num\" = \"1\"\n"
|
||||
+ ")\n",
|
||||
"CREATE TABLE IF NOT EXISTS T2 (\n"
|
||||
+ " id bigint,\n"
|
||||
+ " score bigint\n"
|
||||
+ ")\n"
|
||||
+ "DUPLICATE KEY(id)\n"
|
||||
+ "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
|
||||
+ "PROPERTIES (\n"
|
||||
+ " \"replication_num\" = \"1\"\n"
|
||||
+ ")\n"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runBeforeEach() throws Exception {
|
||||
NamedExpressionUtil.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTranslateCase() throws Exception {
|
||||
for (String sql : testSql) {
|
||||
NamedExpressionUtil.clear();
|
||||
System.out.println("\n\n***** " + sql + " *****\n\n");
|
||||
PhysicalPlan plan = new NereidsPlanner(connectContext).plan(
|
||||
parser.parseSingle(sql),
|
||||
PhysicalProperties.ANY,
|
||||
connectContext
|
||||
);
|
||||
// Just to check whether translate will throw exception
|
||||
new PhysicalPlanTranslator().translatePlan(plan, new PlanTranslatorContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseSubQuery() {
|
||||
FieldChecker projectChecker = new FieldChecker(ImmutableList.of("projects"));
|
||||
new PlanChecker().plan(new NereidsAnalyzer(connectContext).analyze(testSql.get(0)))
|
||||
.applyTopDown(new EliminateAliasNode())
|
||||
.matches(
|
||||
logicalProject(
|
||||
logicalProject(
|
||||
logicalOlapScan().when(o -> true)
|
||||
).when(projectChecker.check(ImmutableList.of(ImmutableList.of(
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("T")),
|
||||
new SlotReference(new ExprId(1), "score", new BigIntType(), true, ImmutableList.of("T")))))
|
||||
)
|
||||
).when(projectChecker.check(ImmutableList.of(ImmutableList.of(
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("T2")),
|
||||
new SlotReference(new ExprId(1), "score", new BigIntType(), true, ImmutableList.of("T2")))))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseMixed() {
|
||||
FieldChecker projectChecker = new FieldChecker(ImmutableList.of("projects"));
|
||||
FieldChecker joinChecker = new FieldChecker(ImmutableList.of("joinType", "condition"));
|
||||
new PlanChecker().plan(new NereidsAnalyzer(connectContext).analyze(testSql.get(1)))
|
||||
.applyTopDown(new EliminateAliasNode())
|
||||
.matches(
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan(),
|
||||
logicalProject(
|
||||
logicalOlapScan()
|
||||
).when(projectChecker.check(ImmutableList.of(ImmutableList.of(
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("TT2")),
|
||||
new SlotReference(new ExprId(1), "score", new BigIntType(), true, ImmutableList.of("TT2")))))
|
||||
)
|
||||
).when(joinChecker.check(ImmutableList.of(
|
||||
JoinType.INNER_JOIN,
|
||||
Optional.of(new EqualTo(
|
||||
new SlotReference(new ExprId(2), "id", new BigIntType(), true, ImmutableList.of("TT1")),
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("T"))))))
|
||||
)
|
||||
).when(projectChecker.check(ImmutableList.of(ImmutableList.of(
|
||||
new SlotReference(new ExprId(2), "id", new BigIntType(), true, ImmutableList.of("TT1")),
|
||||
new SlotReference(new ExprId(3), "score", new BigIntType(), true, ImmutableList.of("TT1")),
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("T")),
|
||||
new SlotReference(new ExprId(1), "score", new BigIntType(), true, ImmutableList.of("T")))))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseJoinSameTable() {
|
||||
FieldChecker projectChecker = new FieldChecker(ImmutableList.of("projects"));
|
||||
FieldChecker joinChecker = new FieldChecker(ImmutableList.of("joinType", "condition"));
|
||||
new PlanChecker().plan(new NereidsAnalyzer(connectContext).analyze(testSql.get(5)))
|
||||
.applyTopDown(new EliminateAliasNode())
|
||||
.matches(
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan(),
|
||||
logicalOlapScan()
|
||||
).when(joinChecker.check(ImmutableList.of(
|
||||
JoinType.INNER_JOIN,
|
||||
Optional.of(new EqualTo(
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("default_cluster:test", "T1")),
|
||||
new SlotReference(new ExprId(2), "id", new BigIntType(), true, ImmutableList.of("T2"))))))
|
||||
)
|
||||
).when(projectChecker.check(ImmutableList.of(ImmutableList.of(
|
||||
new SlotReference(new ExprId(0), "id", new BigIntType(), true, ImmutableList.of("default_cluster:test", "T1")),
|
||||
new SlotReference(new ExprId(1), "score", new BigIntType(), true, ImmutableList.of("default_cluster:test", "T1")),
|
||||
new SlotReference(new ExprId(2), "id", new BigIntType(), true, ImmutableList.of("T2")),
|
||||
new SlotReference(new ExprId(3), "score", new BigIntType(), true, ImmutableList.of("T2")))))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
// 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.util;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class FieldChecker {
|
||||
|
||||
public final List<String> fields;
|
||||
|
||||
public FieldChecker(List<String> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public <T> Predicate<T> check(List<Object> valueList) {
|
||||
return (o) -> {
|
||||
Assertions.assertEquals(fields.size(), valueList.size());
|
||||
Class<?> classInfo = o.getClass();
|
||||
IntStream.range(0, valueList.size()).forEach(i -> {
|
||||
Field field;
|
||||
try {
|
||||
field = classInfo.getDeclaredField(this.fields.get(i));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Assertions.assertEquals(valueList.get(i), field.get(o));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -66,6 +66,7 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
|
||||
import java.io.File;
|
||||
@ -112,12 +113,20 @@ public abstract class TestWithFeService {
|
||||
cleanDorisFeDir(runningDir);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public final void beforeEach() throws Exception {
|
||||
runBeforeEach();
|
||||
}
|
||||
|
||||
protected void runBeforeAll() throws Exception {
|
||||
}
|
||||
|
||||
protected void runAfterAll() throws Exception {
|
||||
}
|
||||
|
||||
protected void runBeforeEach() throws Exception {
|
||||
}
|
||||
|
||||
// Help to create a mocked ConnectContext.
|
||||
protected ConnectContext createDefaultCtx() throws IOException {
|
||||
return createCtx(UserIdentity.ROOT, "127.0.0.1");
|
||||
|
||||
Reference in New Issue
Block a user