com.hazelcast.org.apache.calcite.plan.volcano.Dumpers Maven / Gradle / Ivy
/*
* 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 com.hazelcast.org.apache.calcite.plan.volcano;
import com.hazelcast.org.apache.calcite.avatica.util.Spaces;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.RelVisitor;
import com.hazelcast.org.apache.calcite.rel.metadata.RelMetadataQuery;
import com.hazelcast.org.apache.calcite.util.PartiallyOrderedSet;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.com.google.common.collect.Ordering;
import org.apiguardian.api.API;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Utility class to dump state of VolcanoPlanner
.
*/
@API(since = "1.23", status = API.Status.INTERNAL)
class Dumpers {
private Dumpers() {}
/**
* Returns a multi-line string describing the provenance of a tree of
* relational expressions. For each node in the tree, prints the rule that
* created the node, if any. Recursively describes the provenance of the
* relational expressions that are the arguments to that rule.
*
* Thus, every relational expression and rule invocation that affected
* the final outcome is described in the provenance. This can be useful
* when finding the root cause of "mistakes" in a query plan.
*
* @param provenanceMap The provenance map
* @param root Root relational expression in a tree
* @return Multi-line string describing the rules that created the tree
*/
static String provenance(
Map provenanceMap, RelNode root) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
final List nodes = new ArrayList<>();
new RelVisitor() {
public void visit(RelNode node, int ordinal, RelNode parent) {
nodes.add(node);
super.visit(node, ordinal, parent);
}
// CHECKSTYLE: IGNORE 1
}.go(root);
final Set visited = new HashSet<>();
for (RelNode node : nodes) {
provenanceRecurse(provenanceMap, pw, node, 0, visited);
}
pw.flush();
return sw.toString();
}
private static void provenanceRecurse(
Map provenanceMap,
PrintWriter pw, RelNode node, int i, Set visited) {
Spaces.append(pw, i * 2);
if (!visited.add(node)) {
pw.println("rel#" + node.getId() + " (see above)");
return;
}
pw.println(node);
final VolcanoPlanner.Provenance o = provenanceMap.get(node);
Spaces.append(pw, i * 2 + 2);
if (o == VolcanoPlanner.Provenance.EMPTY) {
pw.println("no parent");
} else if (o instanceof VolcanoPlanner.DirectProvenance) {
RelNode rel = ((VolcanoPlanner.DirectProvenance) o).source;
pw.println("direct");
provenanceRecurse(provenanceMap, pw, rel, i + 2, visited);
} else if (o instanceof VolcanoPlanner.RuleProvenance) {
VolcanoPlanner.RuleProvenance rule = (VolcanoPlanner.RuleProvenance) o;
pw.println("call#" + rule.callId + " rule [" + rule.rule + "]");
for (RelNode rel : rule.rels) {
provenanceRecurse(provenanceMap, pw, rel, i + 2, visited);
}
} else if (o == null && node instanceof RelSubset) {
// A few operands recognize subsets, not individual rels.
// The first rel in the subset is deemed to have created it.
final RelSubset subset = (RelSubset) node;
pw.println("subset " + subset);
provenanceRecurse(provenanceMap, pw,
subset.getRelList().get(0), i + 2, visited);
} else {
throw new AssertionError("bad type " + o);
}
}
static void dumpSets(VolcanoPlanner planner, PrintWriter pw) {
Ordering ordering = Ordering.from(Comparator.comparingInt(o -> o.id));
for (RelSet set : ordering.immutableSortedCopy(planner.allSets)) {
pw.println("Set#" + set.id
+ ", type: " + set.subsets.get(0).getRowType());
int j = -1;
for (RelSubset subset : set.subsets) {
++j;
pw.println(
"\t" + subset + ", best="
+ ((subset.best == null) ? "null"
: ("rel#" + subset.best.getId())));
assert subset.set == set;
for (int k = 0; k < j; k++) {
assert !set.subsets.get(k).getTraitSet().equals(
subset.getTraitSet());
}
for (RelNode rel : subset.getRels()) {
// "\t\trel#34:JavaProject(rel#32:JavaFilter(...), ...)"
pw.print("\t\t" + rel);
for (RelNode input : rel.getInputs()) {
RelSubset inputSubset =
planner.getSubset(
input,
input.getTraitSet());
RelSet inputSet = inputSubset.set;
if (input instanceof RelSubset) {
final Iterator rels =
inputSubset.getRels().iterator();
if (rels.hasNext()) {
input = rels.next();
assert input.getTraitSet().satisfies(inputSubset.getTraitSet());
assert inputSet.rels.contains(input);
assert inputSet.subsets.contains(inputSubset);
}
}
}
if (planner.prunedNodes.contains(rel)) {
pw.print(", pruned");
}
RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
pw.print(", rowcount=" + mq.getRowCount(rel));
pw.println(", cumulative cost=" + planner.getCost(rel, mq));
}
}
}
}
static void dumpGraphviz(VolcanoPlanner planner, PrintWriter pw) {
Ordering ordering = Ordering.from(Comparator.comparingInt(o -> o.id));
Set 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 subsetPoset = new PartiallyOrderedSet<>(
(e1, e2) -> e1.getTraitSet().satisfies(e2.getTraitSet()));
Set 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
String traits = "." + planner.getSubset(rel).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);
RelSubset relSubset = planner.getSubset(rel);
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) {
for (RelSubset parent : subsetPoset.getChildren(subset)) {
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(planner.root.getId());
pw.println(";");
for (RelSet set : ordering.immutableSortedCopy(planner.allSets)) {
for (RelNode rel : set.rels) {
RelSubset relSubset = planner.getSubset(rel);
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 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("}");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy