This is an automated email from the ASF dual-hosted git repository. xingtanzjr pushed a commit to branch xingtanzjr/graph_visitor in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 9c7cef73f9cecc0efe6e4fed53b11b6e2eee01a1 Author: Jinrui.Zhang <[email protected]> AuthorDate: Tue Apr 12 15:44:20 2022 +0800 complete basic PlanGraphPrinter --- .../org/apache/iotdb/db/mpp/common/QueryId.java | 2 +- .../sql/planner/plan/node/PlanGraphPrinter.java | 266 +++++++++++++++++++++ .../db/mpp/sql/planner/plan/node/PlanNode.java | 6 + .../db/mpp/sql/plan/DistributionPlannerTest.java | 2 + 4 files changed, 275 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java b/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java index 5bfeb25e26..54905e3c20 100644 --- a/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java +++ b/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java @@ -48,7 +48,7 @@ public class QueryId { } public PlanNodeId genPlanNodeId() { - return new PlanNodeId(String.format("%s_%d", id, nextPlanNodeIndex++)); + return new PlanNodeId(String.format("%d", nextPlanNodeIndex++)); } public PlanFragmentId genPlanFragmentId() { diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanGraphPrinter.java b/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanGraphPrinter.java new file mode 100644 index 0000000000..7010a58836 --- /dev/null +++ b/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanGraphPrinter.java @@ -0,0 +1,266 @@ +/* + * 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.iotdb.db.mpp.sql.planner.plan.node; + +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PlanGraphPrinter extends PlanVisitor<List<String>, PlanGraphPrinter.GraphContext> { + + private static final String INDENT = " "; + private static final String HENG = "─"; + private static final String SHU = "│"; + private static final String LEFT_BOTTOM = "└"; + private static final String RIGHT_BOTTOM = "┘"; + private static final String LEFT_TOP = "┌"; + private static final String RIGHT_TOP = "┐"; + private static final String SHANG = "┴"; + private static final String XIA = "┬"; + private static final String CROSS = "┼"; + + private static final int BOX_MARGIN = 1; + private static final int CONNECTION_LINE_HEIGHT = 2; + + @Override + public List<String> visitPlan(PlanNode node, GraphContext context) { + List<String> boxValue = new ArrayList<>(); + boxValue.add(String.format("PlanNode-%s", node.getPlanNodeId().getId())); + return render(node, boxValue, context); + } + + private List<String> render(PlanNode node, List<String> nodeBoxString, GraphContext context) { + Box box = new Box(nodeBoxString); + List<List<String>> children = new ArrayList<>(); + for(PlanNode child : node.getChildren()) { + children.add(child.accept(this, context)); + } + box.calculateBoxParams(children); + + box.lines.add(printBoxEdge(box, true)); + for (String valueLine : nodeBoxString) { + StringBuilder line = new StringBuilder(); + for (int i = 0; i < box.lineWidth; i++) { + if (i < box.startPosition) { + line.append(INDENT); + continue; + } + if (i > box.endPosition) { + line.append(INDENT); + continue; + } + if (i == box.startPosition || i == box.endPosition) { + line.append(SHU); + continue; + } + if (i - box.startPosition - 1 < valueLine.length()) { + line.append(valueLine.charAt(i - box.startPosition - 1)); + } else { + line.append(INDENT); + } + } + box.lines.add(line.toString()); + } + box.lines.add(printBoxEdge(box, false)); + + if (children.size() == 0) { + return box.lines; + } + + // Print Connection Line + if (children.size() == 1) { + for (int i = 0; i < CONNECTION_LINE_HEIGHT; i++) { + StringBuilder line = new StringBuilder(); + for (int j = 0; j < box.lineWidth; j++) { + line.append(j == box.midPosition ? SHU : INDENT); + } + box.lines.add(line.toString()); + } + } else { + Map<Integer, String> symbolMap = new HashMap<>(); + Map<Integer, Boolean> childMidPositionMap = new HashMap<>(); + symbolMap.put(box.midPosition, SHANG); + for (int i = 0; i < children.size(); i++) { + int childMidPosition = getChildMidPosition(children, i); + childMidPositionMap.put(childMidPosition, true); + if (childMidPosition == box.midPosition) { + symbolMap.put(box.midPosition, CROSS); + continue; + } + symbolMap.put( + childMidPosition, i == 0 ? LEFT_TOP : i == children.size() - 1 ? RIGHT_TOP : XIA); + } + StringBuilder line1 = new StringBuilder(); + for (int i = 0; i < box.lineWidth; i++) { + if (i < getChildMidPosition(children, 0) || i > getChildMidPosition(children, children.size() - 1)) { + line1.append(INDENT); + continue; + } + line1.append(symbolMap.getOrDefault(i, HENG)); + } + box.lines.add(line1.toString()); + + for (int row = 1; row < CONNECTION_LINE_HEIGHT; row++) { + StringBuilder nextLine = new StringBuilder(); + for (int i = 0; i < box.lineWidth; i++) { + nextLine.append(childMidPositionMap.containsKey(i) ? SHU : INDENT); + } + box.lines.add(nextLine.toString()); + } + } + + for (int i = 0; i < getChildrenLineCount(children); i++) { + StringBuilder line = new StringBuilder(); + for (int j = 0; j < children.size(); j++) { + line.append(getLine(children, j, i)); + if (j != children.size() - 1) { + for (int m = 0; m < BOX_MARGIN; m++) { + line.append(INDENT); + } + } + } + box.lines.add(line.toString()); + } + return box.lines; + } + + private String printBoxEdge(Box box, boolean isTopEdge) { + StringBuilder line = new StringBuilder(); + for (int i = 0; i < box.lineWidth; i++) { + if (i < box.startPosition) { + line.append(INDENT); + } else if (i > box.endPosition) { + line.append(INDENT); + } else if (i == box.startPosition) { + line.append(isTopEdge ? LEFT_TOP : LEFT_BOTTOM); + } else if (i == box.endPosition) { + line.append(isTopEdge ? RIGHT_TOP : RIGHT_BOTTOM); + } else { + line.append(HENG); + } + } + return line.toString(); + } + + private String getLine(List<List<String>> children, int child, int line) { + if (line < children.get(child).size()) { + return children.get(child).get(line); + } + return genEmptyLine(children.get(child).get(0).length()); + } + + private String genEmptyLine(int lineWidth) { + StringBuilder line = new StringBuilder(); + for (int i = 0; i < lineWidth; i++) { + line.append(INDENT); + } + return line.toString(); + } + + private int getChildrenLineCount(List<List<String>> children) { + int count = 0; + for (List<String> child : children) { + count = Math.max(count, child.size()); + } + return count; + } + + private static int getChildMidPosition(List<List<String>> children, int idx) { + int left = 0; + for (int i = 0; i < idx; i++) { + left += children.get(i).get(0).length(); + left += BOX_MARGIN; + } + left += children.get(idx).get(0).length() / 2; + return left; + } + + private static class Box { + private List<String> boxString; + private int boxWidth; + private int lineWidth; + private List<String> lines; + private int startPosition; + private int endPosition; + private int midPosition; + + public Box(List<String> boxString) { + this.boxString = boxString; + this.boxWidth = getBoxWidth(); + this.lines = new ArrayList<>(); + } + + public int getBoxWidth() { + int width = 0; + for (String line : boxString) { + width = Math.max(width, line.length()); + } + return width + 2; + } + + public String getLine(int idx) { + if (idx < lines.size()) { + return lines.get(idx); + } + return genEmptyLine(lineWidth); + } + + private String genEmptyLine(int lineWidth) { + StringBuilder line = new StringBuilder(); + for (int i = 0; i < lineWidth; i++) { + line.append(INDENT); + } + return line.toString(); + } + + public void calculateBoxParams(List<List<String>> childBoxStrings) { + int childrenWidth = 0; + for (List<String> childBoxString : childBoxStrings) { + Validate.isTrue(childBoxString.size() > 0, "Lines of box string should be greater than 0"); + childrenWidth +=childBoxString.get(0).length(); + } + childrenWidth += childBoxStrings.size() > 1 ? (childBoxStrings.size() - 1) * BOX_MARGIN : 0; + this.lineWidth = Math.max(this.boxWidth, childrenWidth); + this.startPosition = (this.lineWidth - this.boxWidth) / 2; + this.endPosition = this.startPosition + this.boxWidth - 1; + this.midPosition = this.lineWidth / 2; + } + } + + public static class GraphContext { + + } + + public static List<String> getGraph(PlanNode node) { + return node.accept(new PlanGraphPrinter(), new PlanGraphPrinter.GraphContext()); + } + + public static void print(PlanNode node) { + List<String> lines = getGraph(node); + for(String line : lines) { + System.out.println(line); + } + } +} + + diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanNode.java b/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanNode.java index 97075588c9..6636a5b41d 100644 --- a/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanNode.java +++ b/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanNode.java @@ -18,6 +18,8 @@ */ package org.apache.iotdb.db.mpp.sql.planner.plan.node; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils; import org.apache.commons.lang.Validate; @@ -113,4 +115,8 @@ public abstract class PlanNode { public int hashCode() { return Objects.hash(id); } + + public List<String> getBoxString() { + return ImmutableList.of(String.format("PlanNode-%s", getPlanNodeId().getId())); + } } diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java index 03db5bb4ff..24b194e3b2 100644 --- a/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java +++ b/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java @@ -36,6 +36,7 @@ import org.apache.iotdb.db.mpp.sql.planner.DistributionPlanner; import org.apache.iotdb.db.mpp.sql.planner.plan.DistributedQueryPlan; import org.apache.iotdb.db.mpp.sql.planner.plan.LogicalQueryPlan; import org.apache.iotdb.db.mpp.sql.planner.plan.SubPlan; +import org.apache.iotdb.db.mpp.sql.planner.plan.node.PlanGraphPrinter; import org.apache.iotdb.db.mpp.sql.planner.plan.node.PlanNode; import org.apache.iotdb.db.mpp.sql.planner.plan.node.process.LimitNode; import org.apache.iotdb.db.mpp.sql.planner.plan.node.process.TimeJoinNode; @@ -160,6 +161,7 @@ public class DistributionPlannerTest { new DistributionPlanner(analysis, new LogicalQueryPlan(context, root)); PlanNode rootAfterRewrite = planner.rewriteSource(); PlanNode rootWithExchange = planner.addExchangeNode(rootAfterRewrite); + PlanGraphPrinter.print(rootWithExchange); SubPlan subPlan = planner.splitFragment(rootWithExchange); assertEquals(subPlan.getChildren().size(), 2); }
