static void dumpGraphviz()

in core/src/main/java/org/apache/calcite/plan/volcano/Dumpers.java [173:358]


  static void dumpGraphviz(VolcanoPlanner planner, PrintWriter pw) {
    Ordering<RelSet> ordering = Ordering.from(Comparator.comparingInt(o -> o.id));
    Set<RelNode> activeRels = new HashSet<>();
    for (VolcanoRuleCall volcanoRuleCall : planner.ruleCallStack) {
      activeRels.addAll(Arrays.asList(volcanoRuleCall.rels));
    }
    pw.println("digraph G {");
    pw.println("\troot [style=filled,label=\"Root\"];");
    PartiallyOrderedSet<RelSubset> subsetPoset =
        new PartiallyOrderedSet<>(
            (e1, e2) -> e1.getTraitSet().satisfies(e2.getTraitSet()));
    Set<RelSubset> nonEmptySubsets = new HashSet<>();
    for (RelSet set : ordering.immutableSortedCopy(planner.allSets)) {
      pw.print("\tsubgraph cluster");
      pw.print(set.id);
      pw.println("{");
      pw.print("\t\tlabel=");
      Util.printJavaString(pw, "Set " + set.id + " "
          + set.subsets.get(0).getRowType(), false);
      pw.print(";\n");
      for (RelNode rel : set.rels) {
        pw.print("\t\trel");
        pw.print(rel.getId());
        pw.print(" [label=");
        RelMetadataQuery mq = rel.getCluster().getMetadataQuery();

        // Note: rel traitset could be different from its subset.traitset
        // It can happen due to RelTraitset#simplify
        // If the traits are different, we want to keep them on a graph
        RelSubset relSubset = planner.getSubset(rel);
        if (relSubset == null) {
          pw.append("no subset found for rel");
          continue;
        }
        String traits = "." + relSubset.getTraitSet().toString();
        String title = rel.toString().replace(traits, "");
        if (title.endsWith(")")) {
          int openParen = title.indexOf('(');
          if (openParen != -1) {
            // Title is like rel#12:LogicalJoin(left=RelSubset#4,right=RelSubset#3,
            // condition==($2, $0),joinType=inner)
            // so we remove the parenthesis, and wrap parameters to the second line
            // This avoids "too wide" Graphiz boxes, and makes the graph easier to follow
            title = title.substring(0, openParen) + '\n'
                + title.substring(openParen + 1, title.length() - 1);
          }
        }
        Util.printJavaString(pw,
            title
                + "\nrows=" + mq.getRowCount(rel) + ", cost="
                + planner.getCost(rel, mq), false);
        if (!(rel instanceof AbstractConverter)) {
          nonEmptySubsets.add(relSubset);
        }
        if (relSubset.best == rel) {
          pw.print(",color=blue");
        }
        if (activeRels.contains(rel)) {
          pw.print(",style=dashed");
        }
        pw.print(",shape=box");
        pw.println("]");
      }

      subsetPoset.clear();
      for (RelSubset subset : set.subsets) {
        subsetPoset.add(subset);
        pw.print("\t\tsubset");
        pw.print(subset.getId());
        pw.print(" [label=");
        Util.printJavaString(pw, subset.toString(), false);
        boolean empty = !nonEmptySubsets.contains(subset);
        if (empty) {
          // We don't want to iterate over rels when we know the set is not empty
          for (RelNode rel : subset.getRels()) {
            if (!(rel instanceof AbstractConverter)) {
              empty = false;
              break;
            }
          }
          if (empty) {
            pw.print(",color=red");
          }
        }
        if (activeRels.contains(subset)) {
          pw.print(",style=dashed");
        }
        pw.print("]\n");
      }

      for (RelSubset subset : subsetPoset) {
        List<RelSubset> children = subsetPoset.getChildren(subset);
        if (children == null) {
          continue;
        }
        for (RelSubset parent : children) {
          pw.print("\t\tsubset");
          pw.print(subset.getId());
          pw.print(" -> subset");
          pw.print(parent.getId());
          pw.print(";");
        }
      }

      pw.print("\t}\n");
    }
    // Note: it is important that all the links are declared AFTER declaration of the nodes
    // Otherwise Graphviz creates nodes implicitly, and puts them into a wrong cluster
    pw.print("\troot -> subset");
    pw.print(requireNonNull(planner.root, "planner.root").getId());
    pw.println(";");
    for (RelSet set : ordering.immutableSortedCopy(planner.allSets)) {
      for (RelNode rel : set.rels) {
        RelSubset relSubset = planner.getSubset(rel);
        if (relSubset == null) {
          pw.append("no subset found for rel ").print(rel.getId());
          continue;
        }
        pw.print("\tsubset");
        pw.print(relSubset.getId());
        pw.print(" -> rel");
        pw.print(rel.getId());
        if (relSubset.best == rel) {
          pw.print("[color=blue]");
        }
        pw.print(";");
        List<RelNode> inputs = rel.getInputs();
        for (int i = 0; i < inputs.size(); i++) {
          RelNode input = inputs.get(i);
          pw.print(" rel");
          pw.print(rel.getId());
          pw.print(" -> ");
          pw.print(input instanceof RelSubset ? "subset" : "rel");
          pw.print(input.getId());
          if (relSubset.best == rel || inputs.size() > 1) {
            char sep = '[';
            if (relSubset.best == rel) {
              pw.print(sep);
              pw.print("color=blue");
              sep = ',';
            }
            if (inputs.size() > 1) {
              pw.print(sep);
              pw.print("label=\"");
              pw.print(i);
              pw.print("\"");
              // sep = ',';
            }
            pw.print(']');
          }
          pw.print(";");
        }
        pw.println();
      }
    }

    // Draw lines for current rules
    for (VolcanoRuleCall ruleCall : planner.ruleCallStack) {
      pw.print("rule");
      pw.print(ruleCall.id);
      pw.print(" [style=dashed,label=");
      Util.printJavaString(pw, ruleCall.rule.toString(), false);
      pw.print("]");

      RelNode[] rels = ruleCall.rels;
      for (int i = 0; i < rels.length; i++) {
        RelNode rel = rels[i];
        pw.print(" rule");
        pw.print(ruleCall.id);
        pw.print(" -> ");
        pw.print(rel instanceof RelSubset ? "subset" : "rel");
        pw.print(rel.getId());
        pw.print(" [style=dashed");
        if (rels.length > 1) {
          pw.print(",label=\"");
          pw.print(i);
          pw.print("\"");
        }
        pw.print("]");
        pw.print(";");
      }
      pw.println();
    }

    pw.print("}");
  }