You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by xi...@apache.org on 2022/04/12 07:44:38 UTC

[iotdb] 01/01: complete basic PlanGraphPrinter

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 <xi...@gmail.com>
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);
   }