in core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java [624:751]
public RelNode visit(
RelNode p,
int ordinal,
@Nullable RelNode parent) {
final int pId = p.getId();
RelNode prevVisit = visited.get(pId);
if (prevVisit != null) {
// return memoized result of previous visit if available
return prevVisit;
}
if (p instanceof RelSubset) {
RelSubset subset = (RelSubset) p;
RelNode cheapest = subset.best;
if (cheapest == null) {
// Dump the planner's expression pool so we can figure
// out why we reached impasse.
StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
pw.print("There are not enough rules to produce a node with desired properties");
RelTraitSet desiredTraits = subset.getTraitSet();
String sep = ": ";
for (RelTrait trait : desiredTraits) {
pw.print(sep);
pw.print(trait.getTraitDef().getSimpleName());
pw.print("=");
pw.print(trait);
sep = ", ";
}
pw.print(".");
DeadEndFinder finder = new DeadEndFinder();
finder.visit(subset);
if (finder.deadEnds.isEmpty()) {
pw.print(" All the inputs have relevant nodes, however the cost is still infinite.");
} else {
Map<String, Long> problemCounts =
finder.deadEnds.stream()
.filter(deadSubset -> deadSubset.getOriginal() != null)
.map(x -> {
RelNode original = castNonNull(x.getOriginal());
return original.getClass().getSimpleName()
+ traitDiff(original.getTraitSet(), x.getTraitSet());
})
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// Sort problems from most often to less often ones
String problems = problemCounts.entrySet().stream()
.sorted(Comparator.comparingLong(Map.Entry<String, Long>::getValue).reversed())
.map(e -> e.getKey() + (e.getValue() > 1 ? " (" + e.getValue() + " cases)" : ""))
.collect(Collectors.joining(", "));
pw.println();
pw.print("Missing conversion");
pw.print(finder.deadEnds.size() == 1 ? " is " : "s are ");
pw.print(problems);
pw.println();
if (finder.deadEnds.size() == 1) {
pw.print("There is 1 empty subset: ");
}
if (finder.deadEnds.size() > 1) {
pw.println("There are " + finder.deadEnds.size() + " empty subsets:");
}
int i = 0;
int rest = finder.deadEnds.size();
for (RelSubset deadEnd : finder.deadEnds) {
if (finder.deadEnds.size() > 1) {
pw.print("Empty subset ");
pw.print(i);
pw.print(": ");
}
pw.print(deadEnd);
pw.println(", the relevant part of the original plan is as follows");
RelNode original = deadEnd.getOriginal();
if (original != null) {
original.explain(
new RelWriterImpl(pw, SqlExplainLevel.EXPPLAN_ATTRIBUTES, true));
}
i++;
rest--;
if (rest > 0) {
pw.println();
}
if (i >= 10 && rest > 1) {
pw.print("The rest ");
pw.print(rest);
pw.println(" leafs are omitted.");
break;
}
}
}
pw.println();
planner.dump(pw);
pw.flush();
final String dump = sw.toString();
RuntimeException e =
new RelOptPlanner.CannotPlanException(dump);
LOGGER.trace("Caught exception in class={}, method=visit", getClass().getName(), e);
throw e;
}
p = cheapest;
}
if (ordinal != -1) {
if (planner.getListener() != null) {
RelOptListener.RelChosenEvent event =
new RelOptListener.RelChosenEvent(
planner,
p);
planner.getListener().relChosen(event);
}
}
List<RelNode> oldInputs = p.getInputs();
List<RelNode> inputs = new ArrayList<>();
for (int i = 0; i < oldInputs.size(); i++) {
RelNode oldInput = oldInputs.get(i);
RelNode input = visit(oldInput, i, p);
inputs.add(input);
}
if (!inputs.equals(oldInputs)) {
final RelNode pOld = p;
p = p.copy(p.getTraitSet(), inputs);
planner.provenanceMap.put(
p, new VolcanoPlanner.DirectProvenance(pOld));
}
visited.put(pId, p); // memoize result for pId
return p;
}