[feature](Nereids) Add dphyp job (#14485)
This commit is contained in:
@ -28,13 +28,11 @@ import org.apache.doris.nereids.glue.translator.PlanTranslatorContext;
|
||||
import org.apache.doris.nereids.jobs.batch.NereidsRewriteJobExecutor;
|
||||
import org.apache.doris.nereids.jobs.batch.OptimizeRulesJob;
|
||||
import org.apache.doris.nereids.jobs.cascades.DeriveStatsJob;
|
||||
import org.apache.doris.nereids.jobs.rewrite.RewriteTopDownJob;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.processor.post.PlanPostProcessors;
|
||||
import org.apache.doris.nereids.processor.pre.PlanPreprocessors;
|
||||
import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.rules.joinreorder.HyperGraphJoinReorder;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel;
|
||||
@ -213,11 +211,6 @@ public class NereidsPlanner extends Planner {
|
||||
}
|
||||
|
||||
private void joinReorder() {
|
||||
new RewriteTopDownJob(
|
||||
getRoot(),
|
||||
(new HyperGraphJoinReorder()).buildRules(),
|
||||
cascadesContext.getCurrentJobContext()
|
||||
).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -30,6 +30,6 @@ public enum JobType {
|
||||
DERIVE_STATS,
|
||||
TOP_DOWN_REWRITE,
|
||||
VISITOR_REWRITE,
|
||||
BOTTOM_UP_REWRITE
|
||||
;
|
||||
BOTTOM_UP_REWRITE,
|
||||
JOIN_ORDER;
|
||||
}
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
// 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.joinorder;
|
||||
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.jobs.Job;
|
||||
import org.apache.doris.nereids.jobs.JobContext;
|
||||
import org.apache.doris.nereids.jobs.JobType;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.GraphSimplifier;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.HyperGraph;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.SubgraphEnumerator;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver.PlanReceiver;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Join Order job with DPHyp
|
||||
*/
|
||||
public class JoinOrderJob extends Job {
|
||||
private final Group group;
|
||||
|
||||
public JoinOrderJob(Group group, JobContext context) {
|
||||
super(JobType.JOIN_ORDER, context);
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws AnalysisException {
|
||||
Preconditions.checkArgument(!group.isJoinGroup());
|
||||
GroupExpression rootExpr = group.getLogicalExpression();
|
||||
int arity = rootExpr.arity();
|
||||
for (int i = 0; i < arity; i++) {
|
||||
rootExpr.setChild(i, optimizePlan(rootExpr.child(i)));
|
||||
}
|
||||
}
|
||||
|
||||
private Group optimizePlan(Group group) {
|
||||
if (group.isJoinGroup()) {
|
||||
return optimizeJoin(group);
|
||||
}
|
||||
GroupExpression rootExpr = group.getLogicalExpression();
|
||||
int arity = rootExpr.arity();
|
||||
for (int i = 0; i < arity; i++) {
|
||||
rootExpr.setChild(i, optimizePlan(rootExpr.child(i)));
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
private Group optimizeJoin(Group group) {
|
||||
HyperGraph hyperGraph = new HyperGraph();
|
||||
buildGraph(group, hyperGraph);
|
||||
// Right now, we just hardcode the limit with 10000, maybe we need a better way to set it
|
||||
int limit = 10000;
|
||||
PlanReceiver planReceiver = new PlanReceiver(limit);
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(planReceiver, hyperGraph);
|
||||
if (!subgraphEnumerator.enumerate()) {
|
||||
GraphSimplifier graphSimplifier = new GraphSimplifier(hyperGraph);
|
||||
graphSimplifier.simplifyGraph(limit);
|
||||
if (!subgraphEnumerator.enumerate()) {
|
||||
throw new RuntimeException("DPHyp can not enumerate all sub graphs with limit=" + limit);
|
||||
}
|
||||
}
|
||||
return planReceiver.getBestPlan(hyperGraph.getNodesMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* build a hyperGraph for the root group
|
||||
*
|
||||
* @param group root group, should be join type
|
||||
* @param hyperGraph build hyperGraph
|
||||
*/
|
||||
public void buildGraph(Group group, HyperGraph hyperGraph) {
|
||||
if (!group.isJoinGroup()) {
|
||||
hyperGraph.addNode(optimizePlan(group));
|
||||
return;
|
||||
}
|
||||
buildGraph(group.getLogicalExpression().child(0), hyperGraph);
|
||||
buildGraph(group.getLogicalExpression().child(1), hyperGraph);
|
||||
hyperGraph.addEdge(group);
|
||||
}
|
||||
}
|
||||
@ -15,9 +15,9 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
@ -39,8 +39,8 @@ public class CircleDetector {
|
||||
List<Integer> nodes = new ArrayList<>();
|
||||
// stored the dependency of each node
|
||||
List<BitSet> directedEdges = new ArrayList<>();
|
||||
// the nodes are after than this node
|
||||
List<BitSet> subNodes = new ArrayList<>();
|
||||
// whether the node has been visited in dfs
|
||||
|
||||
CircleDetector(int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
@ -67,9 +67,10 @@ public class CircleDetector {
|
||||
int order1 = orders.get(node1);
|
||||
int order2 = orders.get(node2);
|
||||
if (order1 >= order2) {
|
||||
shift(order2, order1 + 1, subNodes.get(order2));
|
||||
shift(order2, order1 + 1, subNodes.get(node2));
|
||||
}
|
||||
for (BitSet nodes : subNodes) {
|
||||
// add all subNodes which contains node1 into subNodes of node2.
|
||||
if (Bitmap.get(nodes, node1)) {
|
||||
Bitmap.or(nodes, subNodes.get(node2));
|
||||
}
|
||||
@ -15,9 +15,9 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
@ -15,12 +15,13 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver.Counter;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.stats.StatsCalculator;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
@ -34,6 +35,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Stack;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* GraphSimplifier is used to simplify HyperGraph {@link HyperGraph}
|
||||
@ -41,6 +44,8 @@ import java.util.PriorityQueue;
|
||||
public class GraphSimplifier {
|
||||
// Note that each index in the graph simplifier is the half of the actual index
|
||||
private final int edgeSize;
|
||||
// Detect the circle when order join
|
||||
private CircleDetector circleDetector;
|
||||
// This is used for cache the intermediate results when calculate the benefit
|
||||
// Note that we store it for the after. Because if we put B after A (t1 Join_A t2 Join_B t3),
|
||||
// B is changed. Therefore, Any step that involves B need to be recalculated.
|
||||
@ -48,14 +53,20 @@ public class GraphSimplifier {
|
||||
private PriorityQueue<BestSimplification> priorityQueue = new PriorityQueue<>();
|
||||
// The graph we are simplifying
|
||||
private HyperGraph graph;
|
||||
// Detect the circle when order join
|
||||
private CircleDetector circleDetector;
|
||||
// It cached the plan in simplification. we don't store it in hyper graph,
|
||||
// because it's just used for simulating join. In fact, the graph simplifier
|
||||
// just generate the partial order of join operator.
|
||||
private HashMap<BitSet, Plan> cachePlan = new HashMap<>();
|
||||
|
||||
GraphSimplifier(HyperGraph graph) {
|
||||
private Stack<SimplificationStep> appliedSteps = new Stack<SimplificationStep>();
|
||||
private Stack<SimplificationStep> unAppliedSteps = new Stack<SimplificationStep>();
|
||||
|
||||
/**
|
||||
* Create a graph simplifier
|
||||
*
|
||||
* @param graph create a graph simplifier to simplify the graph
|
||||
*/
|
||||
public GraphSimplifier(HyperGraph graph) {
|
||||
this.graph = graph;
|
||||
edgeSize = graph.getEdges().size();
|
||||
for (int i = 0; i < edgeSize; i++) {
|
||||
@ -78,6 +89,30 @@ public class GraphSimplifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether all edge has been ordered
|
||||
*
|
||||
* @return if true, all edges has a total order
|
||||
*/
|
||||
public boolean isTotalOrder() {
|
||||
for (int i = 0; i < edgeSize; i++) {
|
||||
for (int j = i + 1; j < edgeSize; j++) {
|
||||
Edge edge1 = graph.getEdge(i);
|
||||
Edge edge2 = graph.getEdge(j);
|
||||
List<BitSet> superset = new ArrayList<>();
|
||||
tryGetSuperset(edge1.getLeft(), edge2.getLeft(), superset);
|
||||
tryGetSuperset(edge1.getLeft(), edge2.getRight(), superset);
|
||||
tryGetSuperset(edge1.getRight(), edge2.getLeft(), superset);
|
||||
tryGetSuperset(edge1.getRight(), edge2.getRight(), superset);
|
||||
if (!circleDetector.checkCircleWithEdge(i, j) && !circleDetector.checkCircleWithEdge(j, i)
|
||||
&& !edge2.isSub(edge1) && !edge1.isSub(edge2) && !superset.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will repeatedly apply simplification steps util the number of csg-cmp pair
|
||||
* is under the limit.
|
||||
@ -87,10 +122,45 @@ public class GraphSimplifier {
|
||||
* @param limit the limit number of the csg-cmp pair
|
||||
*/
|
||||
public boolean simplifyGraph(int limit) {
|
||||
// Now we only support simplify graph until the hyperGraph only contains one plan
|
||||
Preconditions.checkArgument(limit == 1);
|
||||
while (applySimplificationStep()) {
|
||||
Preconditions.checkArgument(limit >= 1);
|
||||
initFirstStep();
|
||||
int lowerBound = 0;
|
||||
int upperBound = 1;
|
||||
|
||||
// Try to probe the largest number of steps to satisfy the limit
|
||||
int numApplySteps = 0;
|
||||
Counter counter = new Counter(limit);
|
||||
SubgraphEnumerator enumerator = new SubgraphEnumerator(counter, graph);
|
||||
while (true) {
|
||||
while (numApplySteps < upperBound) {
|
||||
if (!applySimplificationStep()) {
|
||||
// If we have done all steps but still has more sub graphs
|
||||
// Just return
|
||||
if (!enumerator.enumerate()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
numApplySteps += 1;
|
||||
}
|
||||
if (numApplySteps < upperBound || enumerator.enumerate()) {
|
||||
break;
|
||||
}
|
||||
upperBound *= 2;
|
||||
}
|
||||
|
||||
// Try to search the lowest number of steps to satisfy the limit
|
||||
upperBound = numApplySteps + 1;
|
||||
while (lowerBound < upperBound) {
|
||||
int mid = lowerBound + (upperBound - lowerBound) / 2;
|
||||
applyStepsWithNum(mid);
|
||||
if (enumerator.enumerate()) {
|
||||
upperBound = mid;
|
||||
} else {
|
||||
lowerBound = mid + 1;
|
||||
}
|
||||
}
|
||||
applyStepsWithNum(upperBound);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -100,28 +170,64 @@ public class GraphSimplifier {
|
||||
* @return If there is no other steps, return false.
|
||||
*/
|
||||
public boolean applySimplificationStep() {
|
||||
if (priorityQueue.isEmpty()) {
|
||||
boolean needProcessNeighbor = unAppliedSteps.isEmpty();
|
||||
SimplificationStep bestStep = fetchSimplificationStep();
|
||||
if (bestStep == null) {
|
||||
return false;
|
||||
}
|
||||
BestSimplification bestSimplification = priorityQueue.poll();
|
||||
bestSimplification.isInQueue = false;
|
||||
SimplificationStep bestStep = bestSimplification.getStep();
|
||||
while (!circleDetector.tryAddDirectedEdge(bestStep.beforeIndex, bestStep.afterIndex)) {
|
||||
if (priorityQueue.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
bestSimplification = priorityQueue.poll();
|
||||
bestSimplification.isInQueue = false;
|
||||
bestStep = bestSimplification.getStep();
|
||||
}
|
||||
appliedSteps.push(bestStep);
|
||||
Preconditions.checkArgument(
|
||||
cachePlan.containsKey(bestStep.newLeft) && cachePlan.containsKey(bestStep.newRight),
|
||||
String.format("%s - %s", bestStep.newLeft, bestStep.newRight));
|
||||
graph.modifyEdge(bestStep.afterIndex, bestStep.newLeft, bestStep.newRight);
|
||||
processNeighbors(bestStep.afterIndex, 0, edgeSize);
|
||||
if (needProcessNeighbor) {
|
||||
processNeighbors(bestStep.afterIndex, 0, edgeSize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean unApplySimplificationStep() {
|
||||
Preconditions.checkArgument(appliedSteps.size() > 0);
|
||||
SimplificationStep bestStep = appliedSteps.pop();
|
||||
unAppliedSteps.push(bestStep);
|
||||
graph.modifyEdge(bestStep.afterIndex, bestStep.oldLeft, bestStep.oldRight);
|
||||
// Note we don't need to delete this edge in circleDetector, because we don't need to
|
||||
// recalculate neighbors until all steps applied
|
||||
return true;
|
||||
}
|
||||
|
||||
private @Nullable SimplificationStep fetchSimplificationStep() {
|
||||
if (!unAppliedSteps.isEmpty()) {
|
||||
return unAppliedSteps.pop();
|
||||
}
|
||||
if (priorityQueue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BestSimplification bestSimplification = priorityQueue.poll();
|
||||
bestSimplification.isInQueue = false;
|
||||
SimplificationStep bestStep = bestSimplification.getStep();
|
||||
while (bestSimplification.bestNeighbor == -1 || !circleDetector.tryAddDirectedEdge(bestStep.beforeIndex,
|
||||
bestStep.afterIndex)) {
|
||||
if (priorityQueue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
bestSimplification = priorityQueue.poll();
|
||||
bestSimplification.isInQueue = false;
|
||||
processNeighbors(bestStep.afterIndex, 0, edgeSize);
|
||||
bestStep = bestSimplification.getStep();
|
||||
}
|
||||
return bestStep;
|
||||
}
|
||||
|
||||
private void applyStepsWithNum(int num) {
|
||||
while (appliedSteps.size() < num) {
|
||||
applySimplificationStep();
|
||||
}
|
||||
while (appliedSteps.size() > num) {
|
||||
unApplySimplificationStep();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all neighbors and try to make simplification step
|
||||
* Note that when a given ordering is less advantageous and dropped out,
|
||||
@ -148,11 +254,11 @@ public class GraphSimplifier {
|
||||
}
|
||||
|
||||
// Go through the neighbors with higher index, we only need to recalculate all related steps
|
||||
BestSimplification bestSimplification = simplifications.get(edgeIndex1);
|
||||
bestSimplification.bestNeighbor = -1;
|
||||
for (int edgeIndex2 = Integer.max(beginIndex, edgeIndex1 + 1); edgeIndex2 < endIndex; edgeIndex2 += 1) {
|
||||
BestSimplification bestSimplification = simplifications.get(edgeIndex1);
|
||||
Optional<SimplificationStep> optionalStep = makeSimplificationStep(edgeIndex1, edgeIndex2);
|
||||
if (optionalStep.isPresent()) {
|
||||
bestSimplification.bestNeighbor = -1;
|
||||
trySetSimplificationStep(optionalStep.get(), bestSimplification, edgeIndex1, edgeIndex2);
|
||||
}
|
||||
}
|
||||
@ -160,7 +266,8 @@ public class GraphSimplifier {
|
||||
|
||||
private boolean trySetSimplificationStep(SimplificationStep step, BestSimplification bestSimplification,
|
||||
int index, int neighborIndex) {
|
||||
if (bestSimplification.bestNeighbor == -1 || bestSimplification.getBenefit() <= step.getBenefit()) {
|
||||
if (bestSimplification.bestNeighbor == -1 || bestSimplification.isInQueue == false
|
||||
|| bestSimplification.getBenefit() <= step.getBenefit()) {
|
||||
bestSimplification.bestNeighbor = neighborIndex;
|
||||
bestSimplification.setStep(step);
|
||||
updatePriorityQueue(index);
|
||||
@ -284,14 +391,14 @@ public class GraphSimplifier {
|
||||
}
|
||||
// choose edge1Before2
|
||||
step = new SimplificationStep(benefit, edgeIndex1, edgeIndex2, edge1Before2.getLeft(),
|
||||
edge1Before2.getRight());
|
||||
edge1Before2.getRight(), graph.getEdge(edgeIndex2).getLeft(), graph.getEdge(edgeIndex2).getRight());
|
||||
} else {
|
||||
if (cost2Before1 != 0) {
|
||||
benefit = cost1Before2 / cost2Before1;
|
||||
}
|
||||
// choose edge2Before1
|
||||
step = new SimplificationStep(benefit, edgeIndex2, edgeIndex1, edge2Before1.getLeft(),
|
||||
edge2Before1.getRight());
|
||||
edge2Before1.getRight(), graph.getEdge(edgeIndex1).getLeft(), graph.getEdge(edgeIndex1).getRight());
|
||||
}
|
||||
return step;
|
||||
}
|
||||
@ -308,8 +415,8 @@ public class GraphSimplifier {
|
||||
}
|
||||
|
||||
private double getSimpleCost(Plan plan) {
|
||||
if (plan instanceof GroupPlan) {
|
||||
return ((GroupPlan) plan).getGroup().getStatistics().getRowCount();
|
||||
if (!(plan instanceof LogicalJoin)) {
|
||||
return plan.getGroupExpression().get().getOwnerGroup().getStatistics().getRowCount();
|
||||
}
|
||||
return plan.getGroupExpression().get().getCostByProperties(PhysicalProperties.ANY);
|
||||
}
|
||||
@ -360,11 +467,16 @@ public class GraphSimplifier {
|
||||
int afterIndex;
|
||||
BitSet newLeft;
|
||||
BitSet newRight;
|
||||
BitSet oldLeft;
|
||||
BitSet oldRight;
|
||||
|
||||
SimplificationStep(double benefit, int beforeIndex, int afterIndex, BitSet newLeft, BitSet newRight) {
|
||||
SimplificationStep(double benefit, int beforeIndex, int afterIndex, BitSet newLeft, BitSet newRight,
|
||||
BitSet oldLeft, BitSet oldRight) {
|
||||
this.afterIndex = afterIndex;
|
||||
this.beforeIndex = beforeIndex;
|
||||
this.benefit = benefit;
|
||||
this.oldLeft = oldLeft;
|
||||
this.oldRight = oldRight;
|
||||
this.newLeft = newLeft;
|
||||
this.newRight = newRight;
|
||||
}
|
||||
@ -381,7 +493,7 @@ public class GraphSimplifier {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%d -> %d %.2f : %s-%s", beforeIndex, afterIndex, benefit, newLeft, newRight);
|
||||
return String.format("%d -> %d", beforeIndex, afterIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,16 +15,15 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -41,13 +40,6 @@ import java.util.Set;
|
||||
public class HyperGraph {
|
||||
private List<Edge> edges = new ArrayList<>();
|
||||
private List<Node> nodes = new ArrayList<>();
|
||||
private Plan bestPlan;
|
||||
|
||||
public static HyperGraph fromPlan(Plan plan) {
|
||||
HyperGraph graph = new HyperGraph();
|
||||
graph.buildGraph(plan);
|
||||
return graph;
|
||||
}
|
||||
|
||||
public List<Edge> getEdges() {
|
||||
return edges;
|
||||
@ -57,6 +49,10 @@ public class HyperGraph {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public BitSet getNodesMap() {
|
||||
return Bitmap.newBitmapBetween(0, nodes.size());
|
||||
}
|
||||
|
||||
public Edge getEdge(int index) {
|
||||
return edges.get(index);
|
||||
}
|
||||
@ -65,86 +61,26 @@ public class HyperGraph {
|
||||
return nodes.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the best plan of HyperGraph
|
||||
*
|
||||
* @return return the best plan
|
||||
*/
|
||||
public Plan toPlan() {
|
||||
return bestPlan;
|
||||
}
|
||||
|
||||
public boolean simplify() {
|
||||
GraphSimplifier graphSimplifier = new GraphSimplifier(this);
|
||||
graphSimplifier.initFirstStep();
|
||||
return graphSimplifier.simplifyGraph(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to enumerate all csg-cmp pairs and get the best plan
|
||||
*
|
||||
* @return whether enumerate successfully
|
||||
*/
|
||||
public boolean emitPlan() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean optimize() {
|
||||
return simplify() && emitPlan();
|
||||
}
|
||||
|
||||
public void splitEdgesForNodes() {
|
||||
for (Node node : nodes) {
|
||||
node.splitEdges();
|
||||
}
|
||||
}
|
||||
|
||||
private void buildGraph(Plan plan) {
|
||||
if ((plan instanceof LogicalProject && plan.child(0) instanceof GroupPlan)
|
||||
|| plan instanceof GroupPlan) {
|
||||
nodes.add(new Node(nodes.size(), plan));
|
||||
return;
|
||||
}
|
||||
|
||||
LogicalJoin<? extends Plan, ? extends Plan> join;
|
||||
if (plan instanceof LogicalProject) {
|
||||
LogicalProject<? extends Plan> project = (LogicalProject<? extends Plan>) plan;
|
||||
join = (LogicalJoin<? extends Plan, ? extends Plan>) project.child();
|
||||
|
||||
// Handle project
|
||||
// Ignore the projection expression just using for selection column.
|
||||
// TODO: how to handle Alias and complex project expression
|
||||
} else {
|
||||
join = (LogicalJoin<? extends Plan, ? extends Plan>) plan;
|
||||
}
|
||||
|
||||
// Now we only support inner join with Inside-Project
|
||||
// TODO: Other joins can be added according CD-C algorithm
|
||||
if (join.getJoinType() != JoinType.INNER_JOIN) {
|
||||
Node node = new Node(nodes.size(), plan);
|
||||
nodes.add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
buildGraph(join.left());
|
||||
buildGraph(join.right());
|
||||
addEdge(join);
|
||||
public void addNode(Group group) {
|
||||
Preconditions.checkArgument(!group.isJoinGroup());
|
||||
// TODO: replace plan with group expression or others
|
||||
nodes.add(new Node(nodes.size(), group));
|
||||
}
|
||||
|
||||
private BitSet findNodes(Set<Slot> slots) {
|
||||
BitSet bitSet = Bitmap.newBitmap();
|
||||
for (Node node : nodes) {
|
||||
for (Slot slot : node.getPlan().getOutput()) {
|
||||
if (slots.contains(slot)) {
|
||||
Bitmap.set(bitSet, node.getIndex());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
private void addEdge(LogicalJoin<? extends Plan, ? extends Plan> join) {
|
||||
/**
|
||||
* try to add edge for join group
|
||||
*
|
||||
* @param group The join group
|
||||
*/
|
||||
public void addEdge(Group group) {
|
||||
Preconditions.checkArgument(group.isJoinGroup());
|
||||
LogicalJoin<? extends Plan, ? extends Plan> join = (LogicalJoin) group.getLogicalExpression().getPlan();
|
||||
for (Expression expression : join.getExpressions()) {
|
||||
LogicalJoin singleJoin = new LogicalJoin(join.getJoinType(), ImmutableList.of(expression), join.left(),
|
||||
join.right());
|
||||
@ -165,6 +101,19 @@ public class HyperGraph {
|
||||
// We don't implement this trick now.
|
||||
}
|
||||
|
||||
private BitSet findNodes(Set<Slot> slots) {
|
||||
BitSet bitSet = Bitmap.newBitmap();
|
||||
for (Node node : nodes) {
|
||||
for (Slot slot : node.getPlan().getOutput()) {
|
||||
if (slots.contains(slot)) {
|
||||
Bitmap.set(bitSet, node.getIndex());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Graph simplifier need to update the edge for join ordering
|
||||
*
|
||||
@ -15,14 +15,12 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
@ -33,7 +31,7 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class Node {
|
||||
private final int index;
|
||||
private Plan plan;
|
||||
private Group group;
|
||||
private List<Edge> edges = new ArrayList<>();
|
||||
// We split these into simple edges (only one node on each side) and complex edges (others)
|
||||
// because we can often quickly discard all simple edges by testing the set of interesting nodes
|
||||
@ -43,8 +41,8 @@ public class Node {
|
||||
private List<Edge> simpleEdges = new ArrayList<>();
|
||||
private BitSet complexNeighborhood = new BitSet();
|
||||
|
||||
public Node(int index, Plan plan) {
|
||||
this.plan = plan;
|
||||
public Node(int index, Group group) {
|
||||
this.group = group;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@ -65,7 +63,11 @@ public class Node {
|
||||
throw new RuntimeException(String.format("There is no simple Edge <%d - %s>", index, nodes));
|
||||
} else if (Bitmap.isOverlap(complexNeighborhood, nodes)) {
|
||||
for (Edge edge : complexEdges) {
|
||||
if (Bitmap.isSubset(edge.getLeft(), nodes) || Bitmap.isSubset(edge.getRight(), nodes)) {
|
||||
// TODO: Right now we check all edges. But due to the simple cmp, we can only check that the edge with
|
||||
// one side that equal to this node
|
||||
if ((Bitmap.isSubset(edge.getLeft(), nodes) && Bitmap.isSubset(edge.getRight(),
|
||||
Bitmap.newBitmap(index))) || (Bitmap.isSubset(edge.getRight(), nodes) && Bitmap.isSubset(
|
||||
edge.getLeft(), Bitmap.newBitmap(index)))) {
|
||||
return edge;
|
||||
}
|
||||
}
|
||||
@ -82,12 +84,12 @@ public class Node {
|
||||
}
|
||||
|
||||
public Plan getPlan() {
|
||||
return plan;
|
||||
return group.getLogicalExpression().getPlan();
|
||||
}
|
||||
|
||||
public void setPlan(Plan plan) {
|
||||
this.plan = plan;
|
||||
}
|
||||
// public void setPlan(Plan plan) {
|
||||
// this.plan = plan;
|
||||
// }
|
||||
|
||||
public List<Edge> getComplexEdges() {
|
||||
return complexEdges;
|
||||
@ -137,6 +139,10 @@ public class Node {
|
||||
* by graph simplifier.
|
||||
*/
|
||||
public void splitEdges() {
|
||||
simpleEdges.clear();
|
||||
Bitmap.clear(simpleNeighborhood);
|
||||
complexEdges.clear();
|
||||
Bitmap.clear(complexNeighborhood);
|
||||
for (Edge edge : edges) {
|
||||
if (edge.isSimple()) {
|
||||
simpleEdges.add(edge);
|
||||
@ -153,12 +159,14 @@ public class Node {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
Preconditions.checkArgument(plan instanceof GroupPlan, "Each node is a group plan in child");
|
||||
return ((GroupPlan) plan).getGroup().getLogicalExpression().getPlan().getType().name() + index;
|
||||
return getPlan().getType().name() + index;
|
||||
}
|
||||
|
||||
public double getRowCount() {
|
||||
Preconditions.checkArgument(plan instanceof GroupPlan, "Each node is a group plan in child");
|
||||
return ((GroupPlan) plan).getGroup().getStatistics().getRowCount();
|
||||
return group.getStatistics().getRowCount();
|
||||
}
|
||||
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@ -15,11 +15,11 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.SubsetIterator;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.receiver.AbstractReceiver;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.SubsetIterator;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver.AbstractReceiver;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
@ -35,7 +35,7 @@ public class SubgraphEnumerator {
|
||||
//The enumerated hyperGraph
|
||||
HyperGraph hyperGraph;
|
||||
|
||||
SubgraphEnumerator(AbstractReceiver receiver, HyperGraph hyperGraph) {
|
||||
public SubgraphEnumerator(AbstractReceiver receiver, HyperGraph hyperGraph) {
|
||||
this.receiver = receiver;
|
||||
this.hyperGraph = hyperGraph;
|
||||
}
|
||||
@ -46,10 +46,11 @@ public class SubgraphEnumerator {
|
||||
* @return whether the hyperGraph is enumerated successfully
|
||||
*/
|
||||
public boolean enumerate() {
|
||||
receiver.reset();
|
||||
List<Node> nodes = hyperGraph.getNodes();
|
||||
// Init all nodes in Receiver
|
||||
for (Node node : nodes) {
|
||||
receiver.addPlan(node.getBitSet(), node.getPlan());
|
||||
receiver.addGroup(node.getBitSet(), node.getGroup());
|
||||
}
|
||||
hyperGraph.splitEdgesForNodes();
|
||||
int size = nodes.size();
|
||||
@ -59,32 +60,38 @@ public class SubgraphEnumerator {
|
||||
for (int i = size - 2; i >= 0; i--) {
|
||||
BitSet csg = Bitmap.newBitmap(i);
|
||||
Bitmap.unset(forbiddenNodes, i);
|
||||
emitCsg(csg);
|
||||
enumerateCsgRec(csg, Bitmap.newBitmap(forbiddenNodes));
|
||||
if (!emitCsg(csg) || !enumerateCsgRec(csg, Bitmap.newBitmap(forbiddenNodes))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The general purpose of EnumerateCsgRec is to extend a given set csg, which
|
||||
// induces a connected subgraph of G to a larger set with the same property.
|
||||
private void enumerateCsgRec(BitSet csg, BitSet forbiddenNodes) {
|
||||
private boolean enumerateCsgRec(BitSet csg, BitSet forbiddenNodes) {
|
||||
BitSet neighborhood = calcNeighborhood(csg, forbiddenNodes);
|
||||
SubsetIterator subsetIterator = Bitmap.getSubsetIterator(neighborhood);
|
||||
for (BitSet subset : subsetIterator) {
|
||||
BitSet newCsg = Bitmap.newBitmapUnion(csg, subset);
|
||||
if (receiver.contain(newCsg)) {
|
||||
emitCsg(newCsg);
|
||||
if (!emitCsg(newCsg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Bitmap.or(forbiddenNodes, neighborhood);
|
||||
subsetIterator.reset();
|
||||
for (BitSet subset : subsetIterator) {
|
||||
BitSet newCsg = Bitmap.newBitmapUnion(csg, subset);
|
||||
enumerateCsgRec(newCsg, Bitmap.newBitmap(forbiddenNodes));
|
||||
if (!enumerateCsgRec(newCsg, Bitmap.newBitmap(forbiddenNodes))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void enumerateCmpRec(BitSet csg, BitSet cmp, BitSet forbiddenNodes) {
|
||||
private boolean enumerateCmpRec(BitSet csg, BitSet cmp, BitSet forbiddenNodes) {
|
||||
BitSet neighborhood = calcNeighborhood(cmp, forbiddenNodes);
|
||||
SubsetIterator subsetIterator = new SubsetIterator(neighborhood);
|
||||
for (BitSet subset : subsetIterator) {
|
||||
@ -96,7 +103,9 @@ public class SubgraphEnumerator {
|
||||
for (Edge edge : hyperGraph.getEdges()) {
|
||||
if (Bitmap.isSubset(edge.getLeft(), csg) && Bitmap.isSubset(edge.getRight(), newCmp) || (
|
||||
Bitmap.isSubset(edge.getLeft(), newCmp) && Bitmap.isSubset(edge.getRight(), csg))) {
|
||||
receiver.emitCsgCmp(csg, newCmp, edge);
|
||||
if (!receiver.emitCsgCmp(csg, newCmp, edge)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -106,24 +115,30 @@ public class SubgraphEnumerator {
|
||||
subsetIterator.reset();
|
||||
for (BitSet subset : subsetIterator) {
|
||||
BitSet newCmp = Bitmap.newBitmapUnion(cmp, subset);
|
||||
enumerateCmpRec(csg, newCmp, Bitmap.newBitmap(forbiddenNodes));
|
||||
if (!enumerateCmpRec(csg, newCmp, Bitmap.newBitmap(forbiddenNodes))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// EmitCsg takes as an argument a non-empty, proper subset csg of HyperGraph , which
|
||||
// induces a connected subgraph. It is then responsible to generate the seeds for
|
||||
// all cmp such that (csg, cmp) becomes a csg-cmp-pair.
|
||||
private void emitCsg(BitSet csg) {
|
||||
private boolean emitCsg(BitSet csg) {
|
||||
BitSet forbiddenNodes = Bitmap.newBitmapBetween(0, Bitmap.nextSetBit(csg, 0));
|
||||
Bitmap.or(forbiddenNodes, csg);
|
||||
BitSet neighborhoods = calcNeighborhood(csg, Bitmap.newBitmap(forbiddenNodes));
|
||||
|
||||
for (int nodeIndex : Bitmap.getReverseIterator(neighborhoods)) {
|
||||
BitSet cmp = Bitmap.newBitmap(nodeIndex);
|
||||
// whether there is an edge between csg and cmp
|
||||
Node cmpNode = hyperGraph.getNode(nodeIndex);
|
||||
Edge edge = cmpNode.tryGetEdgeWith(csg);
|
||||
if (edge != null) {
|
||||
receiver.emitCsgCmp(csg, cmp, edge);
|
||||
if (!receiver.emitCsgCmp(csg, cmp, edge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In order to avoid enumerate repeated cmp, e.g.,
|
||||
@ -138,8 +153,11 @@ public class SubgraphEnumerator {
|
||||
BitSet newForbiddenNodes = Bitmap.newBitmapBetween(0, nodeIndex + 1);
|
||||
Bitmap.and(newForbiddenNodes, neighborhoods);
|
||||
Bitmap.or(newForbiddenNodes, forbiddenNodes);
|
||||
enumerateCmpRec(csg, cmp, newForbiddenNodes);
|
||||
if (!enumerateCmpRec(csg, cmp, newForbiddenNodes)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is used to calculate neighborhoods of given subgraph.
|
||||
@ -163,9 +181,9 @@ public class SubgraphEnumerator {
|
||||
for (Edge edge : hyperGraph.getEdges()) {
|
||||
BitSet left = edge.getLeft();
|
||||
BitSet right = edge.getRight();
|
||||
if (Bitmap.isSubset(left, subGraph) && !Bitmap.isOverlap(left, forbiddenNodes)) {
|
||||
if (Bitmap.isSubset(left, subGraph) && !Bitmap.isOverlap(right, forbiddenNodes)) {
|
||||
Bitmap.set(neighborhoods, right.nextSetBit(0));
|
||||
} else if (Bitmap.isSubset(right, subGraph) && !Bitmap.isOverlap(right, forbiddenNodes)) {
|
||||
} else if (Bitmap.isSubset(right, subGraph) && !Bitmap.isOverlap(left, forbiddenNodes)) {
|
||||
Bitmap.set(neighborhoods, left.nextSetBit(0));
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.receiver;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.Edge;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.Edge;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
@ -28,9 +28,11 @@ import java.util.BitSet;
|
||||
public interface AbstractReceiver {
|
||||
public boolean emitCsgCmp(BitSet csg, BitSet cmp, Edge edge);
|
||||
|
||||
public void addPlan(BitSet bitSet, Plan plan);
|
||||
public void addGroup(BitSet bitSet, Group group);
|
||||
|
||||
public boolean contain(BitSet bitSet);
|
||||
|
||||
public Plan getBestPlan(BitSet bitSet);
|
||||
public void reset();
|
||||
|
||||
public Group getBestPlan(BitSet bitSet);
|
||||
}
|
||||
@ -15,10 +15,10 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.receiver;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.Edge;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.Edge;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
@ -30,8 +30,18 @@ import java.util.HashMap;
|
||||
*/
|
||||
public class Counter implements AbstractReceiver {
|
||||
// limit define the max number of csg-cmp pair in this Receiver
|
||||
private int limit;
|
||||
private int emitCount = 0;
|
||||
private HashMap<BitSet, Integer> counter = new HashMap<>();
|
||||
|
||||
public Counter() {
|
||||
this.limit = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public Counter(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a new plan from bottom to top
|
||||
*
|
||||
@ -43,6 +53,10 @@ public class Counter implements AbstractReceiver {
|
||||
public boolean emitCsgCmp(BitSet left, BitSet right, Edge edge) {
|
||||
Preconditions.checkArgument(counter.containsKey(left));
|
||||
Preconditions.checkArgument(counter.containsKey(right));
|
||||
emitCount += 1;
|
||||
if (emitCount > limit) {
|
||||
return false;
|
||||
}
|
||||
BitSet bitSet = new BitSet();
|
||||
bitSet.or(left);
|
||||
bitSet.or(right);
|
||||
@ -54,7 +68,7 @@ public class Counter implements AbstractReceiver {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addPlan(BitSet bitSet, Plan plan) {
|
||||
public void addGroup(BitSet bitSet, Group group) {
|
||||
counter.put(bitSet, 1);
|
||||
}
|
||||
|
||||
@ -62,7 +76,12 @@ public class Counter implements AbstractReceiver {
|
||||
return counter.containsKey(bitSet);
|
||||
}
|
||||
|
||||
public Plan getBestPlan(BitSet bitSet) {
|
||||
public void reset() {
|
||||
this.counter.clear();
|
||||
emitCount = 0;
|
||||
}
|
||||
|
||||
public Group getBestPlan(BitSet bitSet) {
|
||||
throw new RuntimeException("Counter does not support getBestPlan()");
|
||||
}
|
||||
|
||||
@ -73,4 +92,8 @@ public class Counter implements AbstractReceiver {
|
||||
public HashMap<BitSet, Integer> getAllCount() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
// 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.joinorder.hypergraph.receiver;
|
||||
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.Edge;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.stats.StatsCalculator;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The Receiver is used for cached the plan that has been emitted and build the new plan
|
||||
*/
|
||||
public class PlanReceiver implements AbstractReceiver {
|
||||
// limit define the max number of csg-cmp pair in this Receiver
|
||||
HashMap<BitSet, Group> planTable = new HashMap<>();
|
||||
int limit;
|
||||
int emitCount = 0;
|
||||
|
||||
public PlanReceiver() {
|
||||
limit = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public PlanReceiver(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a new plan from bottom to top
|
||||
*
|
||||
* @param left the bitmap of left child tree
|
||||
* @param right the bitmap of the right child tree
|
||||
* @param edge the join operator
|
||||
* @return the left and the right can be connected by the edge
|
||||
*/
|
||||
@Override
|
||||
public boolean emitCsgCmp(BitSet left, BitSet right, Edge edge) {
|
||||
Preconditions.checkArgument(planTable.containsKey(left));
|
||||
Preconditions.checkArgument(planTable.containsKey(right));
|
||||
emitCount += 1;
|
||||
if (emitCount > limit) {
|
||||
return false;
|
||||
}
|
||||
BitSet fullKey = Bitmap.newBitmapUnion(left, right);
|
||||
Group group1 = constructGroup(left, right, edge);
|
||||
Group group2 = constructGroup(right, left, edge);
|
||||
Group winnerGroup;
|
||||
if (group1.getLogicalExpression().getCostByProperties(PhysicalProperties.ANY) < group2.getLogicalExpression()
|
||||
.getCostByProperties(PhysicalProperties.ANY)) {
|
||||
winnerGroup = group1;
|
||||
} else {
|
||||
winnerGroup = group2;
|
||||
}
|
||||
|
||||
if (!planTable.containsKey(fullKey)
|
||||
|| planTable.get(fullKey).getLogicalExpression().getCostByProperties(PhysicalProperties.ANY)
|
||||
> winnerGroup.getLogicalExpression().getCostByProperties(PhysicalProperties.ANY)) {
|
||||
planTable.put(fullKey, winnerGroup);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGroup(BitSet bitSet, Group group) {
|
||||
planTable.put(bitSet, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contain(BitSet bitSet) {
|
||||
return planTable.containsKey(bitSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
planTable.clear();
|
||||
emitCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getBestPlan(BitSet bitSet) {
|
||||
Preconditions.checkArgument(planTable.containsKey(bitSet));
|
||||
return planTable.get(bitSet);
|
||||
}
|
||||
|
||||
private double getSimpleCost(Plan plan) {
|
||||
if (!(plan instanceof LogicalJoin)) {
|
||||
return plan.getGroupExpression().get().getOwnerGroup().getStatistics().getRowCount();
|
||||
}
|
||||
return plan.getGroupExpression().get().getCostByProperties(PhysicalProperties.ANY);
|
||||
}
|
||||
|
||||
private Group constructGroup(BitSet left, BitSet right, Edge edge) {
|
||||
Preconditions.checkArgument(planTable.containsKey(left));
|
||||
Preconditions.checkArgument(planTable.containsKey(right));
|
||||
Group leftGroup = planTable.get(left);
|
||||
Group rightGroup = planTable.get(right);
|
||||
Plan leftPlan = leftGroup.getLogicalExpression().getPlan();
|
||||
Plan rightPlan = rightGroup.getLogicalExpression().getPlan();
|
||||
|
||||
double cost = getSimpleCost(leftPlan) + getSimpleCost(rightPlan);
|
||||
LogicalJoin newJoin = new LogicalJoin(edge.getJoin().getJoinType(), edge.getJoin().getExpressions(),
|
||||
leftGroup.getLogicalExpression().getPlan(),
|
||||
rightGroup.getLogicalExpression().getPlan());
|
||||
|
||||
GroupExpression groupExpression = new GroupExpression(newJoin, Lists.newArrayList(leftGroup, rightGroup));
|
||||
Group group = new Group();
|
||||
group.addGroupExpression(groupExpression);
|
||||
StatsCalculator.estimate(groupExpression);
|
||||
cost += group.getStatistics().getRowCount();
|
||||
|
||||
groupExpression.updateLowestCostTable(PhysicalProperties.ANY,
|
||||
Lists.newArrayList(PhysicalProperties.ANY, PhysicalProperties.ANY), cost);
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ package org.apache.doris.nereids.memo;
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.nereids.properties.LogicalProperties;
|
||||
import org.apache.doris.nereids.properties.PhysicalProperties;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.nereids.util.TreeStringUtils;
|
||||
import org.apache.doris.statistics.StatsDeriveResult;
|
||||
@ -316,6 +317,10 @@ public class Group {
|
||||
lowestCostPlans.clear();
|
||||
}
|
||||
|
||||
public boolean isJoinGroup() {
|
||||
return getLogicalExpression().getPlan() instanceof LogicalJoin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
||||
@ -105,6 +105,11 @@ public class GroupExpression {
|
||||
return children.get(i);
|
||||
}
|
||||
|
||||
public void setChild(int i, Group group) {
|
||||
children.set(i, group);
|
||||
group.addParentExpression(this);
|
||||
}
|
||||
|
||||
public List<Group> children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder;
|
||||
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.HyperGraph;
|
||||
import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
|
||||
/**
|
||||
* This rule is for Join Reorder (non Cascades Transfrom Join Reorder).
|
||||
*/
|
||||
public class HyperGraphJoinReorder extends OneRewriteRuleFactory {
|
||||
@Override
|
||||
public Rule build() {
|
||||
// TODO: we need a pattern to match a subtree of join and mark the node in this tree ordered
|
||||
return logicalJoin(
|
||||
subTree(LogicalJoin.class, LogicalProject.class),
|
||||
subTree(LogicalJoin.class, LogicalProject.class))
|
||||
.thenApply(ctx -> {
|
||||
LogicalJoin<? extends Plan, ? extends Plan> rootJoin = ctx.root;
|
||||
// TODO: check mark.
|
||||
HyperGraph graph = HyperGraph.fromPlan(rootJoin);
|
||||
if (graph.optimize()) {
|
||||
return graph.toPlan();
|
||||
}
|
||||
return null;
|
||||
}).toRule(RuleType.JOIN_REORDER);
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder;
|
||||
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.HyperGraph;
|
||||
import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
|
||||
/**
|
||||
* This rule is for Join Reorder (non Cascades Transfrom Join Reorder).
|
||||
*/
|
||||
public class HyperGraphJoinReorderGroupLeft extends OneRewriteRuleFactory {
|
||||
@Override
|
||||
public Rule build() {
|
||||
// TODO: we need a pattern to match a subtree of join and mark the node in this tree ordered
|
||||
return logicalJoin(
|
||||
group(),
|
||||
subTree(LogicalJoin.class, LogicalProject.class))
|
||||
.thenApply(ctx -> {
|
||||
LogicalJoin<? extends Plan, ? extends Plan> rootJoin = ctx.root;
|
||||
HyperGraph graph = HyperGraph.fromPlan(rootJoin);
|
||||
System.out.println(graph.toDottyHyperGraph());
|
||||
if (graph.optimize()) {
|
||||
return graph.toPlan();
|
||||
}
|
||||
return null;
|
||||
}).toRule(RuleType.JOIN_REORDER);
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder;
|
||||
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.HyperGraph;
|
||||
import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
|
||||
/**
|
||||
* This rule is for Join Reorder (non Cascades Transfrom Join Reorder).
|
||||
*/
|
||||
public class HyperGraphJoinReorderGroupRight extends OneRewriteRuleFactory {
|
||||
@Override
|
||||
public Rule build() {
|
||||
// TODO: we need a pattern to match a subtree of join and mark the node in this tree ordered
|
||||
return logicalJoin(
|
||||
subTree(LogicalJoin.class, LogicalProject.class),
|
||||
group())
|
||||
.thenApply(ctx -> {
|
||||
LogicalJoin<? extends Plan, ? extends Plan> rootJoin = ctx.root;
|
||||
HyperGraph graph = HyperGraph.fromPlan(rootJoin);
|
||||
if (graph.optimize()) {
|
||||
return graph.toPlan();
|
||||
}
|
||||
return null;
|
||||
}).toRule(RuleType.JOIN_REORDER);
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph.receiver;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.Edge;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The Receiver is used for cached the plan that has been emitted and build the new plan
|
||||
*/
|
||||
public class PlanTable implements AbstractReceiver {
|
||||
// limit define the max number of csg-cmp pair in this Receiver
|
||||
HashMap<BitSet, Integer> counter;
|
||||
|
||||
/**
|
||||
* Emit a new plan from bottom to top
|
||||
*
|
||||
* @param left the bitmap of left child tree
|
||||
* @param right the bitmap of the right child tree
|
||||
* @param edge the join operator
|
||||
* @return the left and the right can be connected by the edge
|
||||
*/
|
||||
public boolean emitCsgCmp(BitSet left, BitSet right, Edge edge) {
|
||||
throw new RuntimeException("PlanTable does not support emitCsgCmp()");
|
||||
}
|
||||
|
||||
public void addPlan(BitSet bitSet, Plan plan) {
|
||||
throw new RuntimeException("PlanTable does not support addPlan()");
|
||||
}
|
||||
|
||||
public boolean contain(BitSet bitSet) {
|
||||
throw new RuntimeException("PlanTable does not support contain()");
|
||||
}
|
||||
|
||||
public Plan getBestPlan(BitSet bitSet) {
|
||||
throw new RuntimeException("PlanTable does not support getBestPlan()");
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder;
|
||||
package org.apache.doris.nereids.jobs.joinorder;
|
||||
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
@ -26,17 +26,18 @@ import org.apache.doris.nereids.util.MemoTestUtils;
|
||||
import org.apache.doris.nereids.util.PlanChecker;
|
||||
import org.apache.doris.nereids.util.PlanConstructor;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class HyperGraphJoinReorderGroupLeftTest {
|
||||
private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0);
|
||||
private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0);
|
||||
private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0);
|
||||
private final LogicalOlapScan scan4 = PlanConstructor.newLogicalOlapScan(3, "t4", 0);
|
||||
private final LogicalOlapScan scan5 = PlanConstructor.newLogicalOlapScan(4, "t5", 0);
|
||||
public class JoinOrderJobTest {
|
||||
private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(1, "t1", 0);
|
||||
private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(2, "t2", 0);
|
||||
private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(3, "t3", 0);
|
||||
private final LogicalOlapScan scan4 = PlanConstructor.newLogicalOlapScan(4, "t4", 0);
|
||||
private final LogicalOlapScan scan5 = PlanConstructor.newLogicalOlapScan(5, "t5", 0);
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
void testJoinOrderJob() {
|
||||
LogicalPlan plan = new LogicalPlanBuilder(scan1)
|
||||
.hashJoinUsing(
|
||||
new LogicalPlanBuilder(scan2)
|
||||
@ -46,11 +47,12 @@ class HyperGraphJoinReorderGroupLeftTest {
|
||||
.build(),
|
||||
JoinType.INNER_JOIN, Pair.of(0, 1)
|
||||
)
|
||||
.project(Lists.newArrayList(1))
|
||||
.build();
|
||||
|
||||
System.out.println(plan.treeString());
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.deriveStats()
|
||||
.applyTopDown(new HyperGraphJoinReorderGroupLeft())
|
||||
.orderJoin()
|
||||
.printlnTree();
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,10 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.SubsetIterator;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.SubsetIterator;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@ -15,9 +15,9 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.receiver.Counter;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver.Counter;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.util.HyperGraphBuilder;
|
||||
|
||||
@ -33,7 +33,7 @@ public class GraphSimplifierTest {
|
||||
// |
|
||||
// t2
|
||||
HyperGraph hyperGraph = new HyperGraphBuilder()
|
||||
.init(10, 20, 30, 40, 50)
|
||||
.init(10, 30, 20, 40, 50)
|
||||
.addEdge(JoinType.INNER_JOIN, 0, 1)
|
||||
.addEdge(JoinType.INNER_JOIN, 0, 2)
|
||||
.addEdge(JoinType.INNER_JOIN, 0, 3)
|
||||
@ -47,8 +47,9 @@ public class GraphSimplifierTest {
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(counter, hyperGraph);
|
||||
subgraphEnumerator.enumerate();
|
||||
for (int count : counter.getAllCount().values()) {
|
||||
Assertions.assertEquals(count, 1);
|
||||
Assertions.assertTrue(count < 10);
|
||||
}
|
||||
Assertions.assertTrue(graphSimplifier.isTotalOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -74,8 +75,9 @@ public class GraphSimplifierTest {
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(counter, hyperGraph);
|
||||
subgraphEnumerator.enumerate();
|
||||
for (int count : counter.getAllCount().values()) {
|
||||
Assertions.assertEquals(count, 1);
|
||||
Assertions.assertTrue(count < 10);
|
||||
}
|
||||
Assertions.assertTrue(graphSimplifier.isTotalOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -102,8 +104,9 @@ public class GraphSimplifierTest {
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(counter, hyperGraph);
|
||||
subgraphEnumerator.enumerate();
|
||||
for (int count : counter.getAllCount().values()) {
|
||||
Assertions.assertEquals(count, 1);
|
||||
Assertions.assertTrue(count < 10);
|
||||
}
|
||||
Assertions.assertTrue(graphSimplifier.isTotalOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -135,8 +138,9 @@ public class GraphSimplifierTest {
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(counter, hyperGraph);
|
||||
subgraphEnumerator.enumerate();
|
||||
for (int count : counter.getAllCount().values()) {
|
||||
Assertions.assertEquals(count, 1);
|
||||
Assertions.assertTrue(count < 10);
|
||||
}
|
||||
Assertions.assertTrue(graphSimplifier.isTotalOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -160,8 +164,8 @@ public class GraphSimplifierTest {
|
||||
|
||||
@Test
|
||||
void testRandomQuery() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
HyperGraph hyperGraph = new HyperGraphBuilder().randomBuildWith(10, 40);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
HyperGraph hyperGraph = new HyperGraphBuilder().randomBuildWith(20, 40);
|
||||
GraphSimplifier graphSimplifier = new GraphSimplifier(hyperGraph);
|
||||
graphSimplifier.initFirstStep();
|
||||
while (graphSimplifier.applySimplificationStep()) {
|
||||
@ -170,8 +174,25 @@ public class GraphSimplifierTest {
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(counter, hyperGraph);
|
||||
subgraphEnumerator.enumerate();
|
||||
for (int count : counter.getAllCount().values()) {
|
||||
Assertions.assertEquals(count, 1);
|
||||
Assertions.assertTrue(count < 1000);
|
||||
}
|
||||
Assertions.assertTrue(graphSimplifier.isTotalOrder());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLimit() {
|
||||
int tableNum = 10;
|
||||
int edgeNum = 20;
|
||||
for (int limit = 1000; limit < 10000; limit += 100) {
|
||||
HyperGraph hyperGraph = new HyperGraphBuilder().randomBuildWith(tableNum, edgeNum);
|
||||
GraphSimplifier graphSimplifier = new GraphSimplifier(hyperGraph);
|
||||
graphSimplifier.simplifyGraph(limit);
|
||||
Counter counter = new Counter();
|
||||
SubgraphEnumerator subgraphEnumerator = new SubgraphEnumerator(counter, hyperGraph);
|
||||
subgraphEnumerator.enumerate();
|
||||
Assertions.assertTrue(counter.getLimit() >= 0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.util.HyperGraphBuilder;
|
||||
@ -15,11 +15,11 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder.hypergraph;
|
||||
package org.apache.doris.nereids.jobs.joinorder.hypergraph;
|
||||
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.SubsetIterator;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.receiver.Counter;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.SubsetIterator;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.receiver.Counter;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.util.HyperGraphBuilder;
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder;
|
||||
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.nereids.util.LogicalPlanBuilder;
|
||||
import org.apache.doris.nereids.util.MemoTestUtils;
|
||||
import org.apache.doris.nereids.util.PlanChecker;
|
||||
import org.apache.doris.nereids.util.PlanConstructor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class HyperGraphJoinReorderGroupRightTest {
|
||||
private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0);
|
||||
private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0);
|
||||
private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0);
|
||||
private final LogicalOlapScan scan4 = PlanConstructor.newLogicalOlapScan(3, "t4", 0);
|
||||
private final LogicalOlapScan scan5 = PlanConstructor.newLogicalOlapScan(4, "t5", 0);
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
LogicalPlan plan = new LogicalPlanBuilder(scan1)
|
||||
.hashJoinUsing(scan2, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.hashJoinUsing(scan3, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.hashJoinUsing(scan4, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.hashJoinUsing(scan5, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.deriveStats()
|
||||
.applyTopDown(new HyperGraphJoinReorderGroupRight())
|
||||
.printlnTree();
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.doris.nereids.rules.joinreorder;
|
||||
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.nereids.util.LogicalPlanBuilder;
|
||||
import org.apache.doris.nereids.util.MemoTestUtils;
|
||||
import org.apache.doris.nereids.util.PlanChecker;
|
||||
import org.apache.doris.nereids.util.PlanConstructor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class HyperGraphJoinReorderTest {
|
||||
private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0);
|
||||
private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0);
|
||||
private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0);
|
||||
private final LogicalOlapScan scan4 = PlanConstructor.newLogicalOlapScan(3, "t4", 0);
|
||||
private final LogicalOlapScan scan5 = PlanConstructor.newLogicalOlapScan(4, "t5", 0);
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
LogicalPlan plan = new LogicalPlanBuilder(scan1)
|
||||
.hashJoinUsing(scan2, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.hashJoinUsing(scan3, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.hashJoinUsing(
|
||||
new LogicalPlanBuilder(scan4)
|
||||
.hashJoinUsing(scan5, JoinType.INNER_JOIN, Pair.of(0, 1))
|
||||
.build(),
|
||||
JoinType.INNER_JOIN, Pair.of(0, 1)
|
||||
)
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.deriveStats()
|
||||
.applyTopDown(new HyperGraphJoinReorder())
|
||||
.printlnTree();
|
||||
}
|
||||
}
|
||||
@ -19,17 +19,13 @@ package org.apache.doris.nereids.util;
|
||||
|
||||
import org.apache.doris.common.Pair;
|
||||
import org.apache.doris.nereids.CascadesContext;
|
||||
import org.apache.doris.nereids.pattern.GroupExpressionMatching;
|
||||
import org.apache.doris.nereids.rules.Rule;
|
||||
import org.apache.doris.nereids.rules.joinreorder.HyperGraphJoinReorder;
|
||||
import org.apache.doris.nereids.rules.joinreorder.HyperGraphJoinReorderGroupLeft;
|
||||
import org.apache.doris.nereids.rules.joinreorder.HyperGraphJoinReorderGroupRight;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.HyperGraph;
|
||||
import org.apache.doris.nereids.rules.joinreorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.stats.StatsCalculator;
|
||||
import org.apache.doris.nereids.jobs.cascades.DeriveStatsJob;
|
||||
import org.apache.doris.nereids.jobs.joinorder.JoinOrderJob;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.HyperGraph;
|
||||
import org.apache.doris.nereids.jobs.joinorder.hypergraph.bitmap.Bitmap;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.trees.expressions.EqualTo;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.JoinType;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
@ -38,7 +34,6 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
|
||||
import org.apache.doris.statistics.StatsDeriveResult;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
@ -54,9 +49,7 @@ public class HyperGraphBuilder {
|
||||
public HyperGraph build() {
|
||||
assert plans.size() == 1 : "there are cross join";
|
||||
Plan plan = plans.values().iterator().next();
|
||||
Plan planWithStats = extractJoinCluster(plan);
|
||||
HyperGraph graph = HyperGraph.fromPlan(planWithStats);
|
||||
return graph;
|
||||
return buildHyperGraph(plan);
|
||||
}
|
||||
|
||||
public HyperGraph randomBuildWith(int tableNum, int edgeNum) {
|
||||
@ -168,49 +161,31 @@ public class HyperGraphBuilder {
|
||||
return bitSet.equals(bitSet2);
|
||||
}
|
||||
|
||||
private Rule selectRuleForPlan(Plan plan) {
|
||||
Assertions.assertTrue(plan instanceof LogicalJoin);
|
||||
LogicalJoin join = (LogicalJoin) plan;
|
||||
if (!(join.left() instanceof LogicalJoin)) {
|
||||
return new HyperGraphJoinReorderGroupLeft().build();
|
||||
} else if (!(join.right() instanceof LogicalJoin)) {
|
||||
return new HyperGraphJoinReorderGroupRight().build();
|
||||
}
|
||||
return new HyperGraphJoinReorder().build();
|
||||
}
|
||||
|
||||
private Plan extractJoinCluster(Plan plan) {
|
||||
Rule rule = selectRuleForPlan(plan);
|
||||
private HyperGraph buildHyperGraph(Plan plan) {
|
||||
CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(MemoTestUtils.createConnectContext(),
|
||||
plan);
|
||||
GroupExpressionMatching groupExpressionMatching
|
||||
= new GroupExpressionMatching(rule.getPattern(),
|
||||
cascadesContext.getMemo().getRoot().getLogicalExpression());
|
||||
List<Plan> planList = new ArrayList<>();
|
||||
for (Plan matchingPlan : groupExpressionMatching) {
|
||||
planList.add(matchingPlan);
|
||||
}
|
||||
assert planList.size() == 1 : "Now we only support one join cluster";
|
||||
injectRowcount(planList.get(0));
|
||||
return planList.get(0);
|
||||
JoinOrderJob joinOrderJob = new JoinOrderJob(cascadesContext.getMemo().getRoot(),
|
||||
cascadesContext.getCurrentJobContext());
|
||||
cascadesContext.pushJob(
|
||||
new DeriveStatsJob(cascadesContext.getMemo().getRoot().getLogicalExpression(),
|
||||
cascadesContext.getCurrentJobContext()));
|
||||
cascadesContext.getJobScheduler().executeJobPool(cascadesContext);
|
||||
injectRowcount(cascadesContext.getMemo().getRoot());
|
||||
HyperGraph hyperGraph = new HyperGraph();
|
||||
joinOrderJob.buildGraph(cascadesContext.getMemo().getRoot(), hyperGraph);
|
||||
return hyperGraph;
|
||||
}
|
||||
|
||||
private void injectRowcount(Plan plan) {
|
||||
if (plan instanceof GroupPlan) {
|
||||
GroupPlan olapGroupPlan = (GroupPlan) plan;
|
||||
StatsCalculator.estimate(olapGroupPlan.getGroup().getLogicalExpression());
|
||||
LogicalOlapScan scanPlan = (LogicalOlapScan) olapGroupPlan.getGroup().getLogicalExpression().getPlan();
|
||||
StatsDeriveResult stats = olapGroupPlan.getGroup().getStatistics();
|
||||
olapGroupPlan.getGroup()
|
||||
.setStatistics(stats
|
||||
.updateRowCount(rowCounts.get(Integer.parseInt(scanPlan.getTable().getName()))));
|
||||
private void injectRowcount(Group group) {
|
||||
if (!group.isJoinGroup()) {
|
||||
StatsDeriveResult stats = group.getStatistics();
|
||||
LogicalOlapScan scanPlan = (LogicalOlapScan) group.getLogicalExpression().getPlan();
|
||||
int rowCount = rowCounts.get(Integer.parseInt(scanPlan.getTable().getName()));
|
||||
group.setStatistics(stats.updateRowCount(rowCount));
|
||||
return;
|
||||
}
|
||||
LogicalJoin join = (LogicalJoin) plan;
|
||||
injectRowcount(join.left());
|
||||
injectRowcount(join.right());
|
||||
// Because the children stats has been changed, so we need to recalculate it
|
||||
StatsCalculator.estimate(join.getGroupExpression().get());
|
||||
injectRowcount(group.getLogicalExpression().child(0));
|
||||
injectRowcount(group.getLogicalExpression().child(1));
|
||||
}
|
||||
|
||||
private void addCondition(int node1, int node2, BitSet key) {
|
||||
|
||||
@ -24,6 +24,7 @@ import org.apache.doris.nereids.glue.LogicalPlanAdapter;
|
||||
import org.apache.doris.nereids.jobs.JobContext;
|
||||
import org.apache.doris.nereids.jobs.batch.NereidsRewriteJobExecutor;
|
||||
import org.apache.doris.nereids.jobs.cascades.DeriveStatsJob;
|
||||
import org.apache.doris.nereids.jobs.joinorder.JoinOrderJob;
|
||||
import org.apache.doris.nereids.memo.Group;
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.memo.Memo;
|
||||
@ -71,6 +72,19 @@ public class PlanChecker {
|
||||
this.cascadesContext = cascadesContext;
|
||||
}
|
||||
|
||||
public static PlanChecker from(ConnectContext connectContext) {
|
||||
return new PlanChecker(connectContext);
|
||||
}
|
||||
|
||||
public static PlanChecker from(ConnectContext connectContext, Plan initPlan) {
|
||||
CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(connectContext, initPlan);
|
||||
return new PlanChecker(cascadesContext);
|
||||
}
|
||||
|
||||
public static PlanChecker from(CascadesContext cascadesContext) {
|
||||
return new PlanChecker(cascadesContext);
|
||||
}
|
||||
|
||||
public PlanChecker checkParse(String sql, Consumer<PlanParseChecker> consumer) {
|
||||
PlanParseChecker checker = new PlanParseChecker(sql);
|
||||
consumer.accept(checker);
|
||||
@ -248,7 +262,15 @@ public class PlanChecker {
|
||||
|
||||
public PlanChecker deriveStats() {
|
||||
cascadesContext.pushJob(
|
||||
new DeriveStatsJob(cascadesContext.getMemo().getRoot().getLogicalExpression(), cascadesContext.getCurrentJobContext()));
|
||||
new DeriveStatsJob(cascadesContext.getMemo().getRoot().getLogicalExpression(),
|
||||
cascadesContext.getCurrentJobContext()));
|
||||
cascadesContext.getJobScheduler().executeJobPool(cascadesContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PlanChecker orderJoin() {
|
||||
cascadesContext.pushJob(
|
||||
new JoinOrderJob(cascadesContext.getMemo().getRoot(), cascadesContext.getCurrentJobContext()));
|
||||
cascadesContext.getJobScheduler().executeJobPool(cascadesContext);
|
||||
return this;
|
||||
}
|
||||
@ -348,19 +370,6 @@ public class PlanChecker {
|
||||
});
|
||||
}
|
||||
|
||||
public static PlanChecker from(ConnectContext connectContext) {
|
||||
return new PlanChecker(connectContext);
|
||||
}
|
||||
|
||||
public static PlanChecker from(ConnectContext connectContext, Plan initPlan) {
|
||||
CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(connectContext, initPlan);
|
||||
return new PlanChecker(cascadesContext);
|
||||
}
|
||||
|
||||
public static PlanChecker from(CascadesContext cascadesContext) {
|
||||
return new PlanChecker(cascadesContext);
|
||||
}
|
||||
|
||||
public CascadesContext getCascadesContext() {
|
||||
return cascadesContext;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user