blob: 0ecca030c006f392a36cf6c6b55f8c3d9733a808 [file] [log] [blame]
/*
* 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.calcite.plan;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.convert.ConverterRule;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.graph.DefaultDirectedGraph;
import org.apache.calcite.util.graph.DefaultEdge;
import org.apache.calcite.util.graph.DirectedGraph;
import org.apache.calcite.util.graph.Graphs;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.List;
import java.util.WeakHashMap;
/**
* Definition of the the convention trait.
* A new set of conversion information is created for
* each planner that registers at least one {@link ConverterRule} instance.
*
* <p>Conversion data is held in a {@link WeakHashMap} so that the JVM's garbage
* collector may reclaim the conversion data after the planner itself has been
* garbage collected. The conversion information consists of a graph of
* conversions (from one calling convention to another) and a map of graph arcs
* to {@link ConverterRule}s.
*/
public class ConventionTraitDef extends RelTraitDef<Convention> {
//~ Static fields/initializers ---------------------------------------------
public static final ConventionTraitDef INSTANCE =
new ConventionTraitDef();
//~ Instance fields --------------------------------------------------------
/**
* Weak-key map of RelOptPlanner to ConversionData. The idea is that when
* the planner goes away, so does the map entry.
*/
private final WeakHashMap<RelOptPlanner, ConversionData>
plannerConversionMap =
new WeakHashMap<RelOptPlanner, ConversionData>();
//~ Constructors -----------------------------------------------------------
private ConventionTraitDef() {
super();
}
//~ Methods ----------------------------------------------------------------
// implement RelTraitDef
public Class<Convention> getTraitClass() {
return Convention.class;
}
public String getSimpleName() {
return "convention";
}
public Convention getDefault() {
return Convention.NONE;
}
public void registerConverterRule(
RelOptPlanner planner,
ConverterRule converterRule) {
if (converterRule.isGuaranteed()) {
ConversionData conversionData = getConversionData(planner);
final Convention inConvention =
(Convention) converterRule.getInTrait();
final Convention outConvention =
(Convention) converterRule.getOutTrait();
conversionData.conversionGraph.addVertex(inConvention);
conversionData.conversionGraph.addVertex(outConvention);
conversionData.conversionGraph.addEdge(inConvention, outConvention);
conversionData.mapArcToConverterRule.put(
Pair.of(inConvention, outConvention), converterRule);
}
}
public void deregisterConverterRule(
RelOptPlanner planner,
ConverterRule converterRule) {
if (converterRule.isGuaranteed()) {
ConversionData conversionData = getConversionData(planner);
final Convention inConvention =
(Convention) converterRule.getInTrait();
final Convention outConvention =
(Convention) converterRule.getOutTrait();
final boolean removed =
conversionData.conversionGraph.removeEdge(
inConvention, outConvention);
assert removed;
conversionData.mapArcToConverterRule.remove(
Pair.of(inConvention, outConvention), converterRule);
}
}
// implement RelTraitDef
public RelNode convert(
RelOptPlanner planner,
RelNode rel,
Convention toConvention,
boolean allowInfiniteCostConverters) {
final RelMetadataQuery mq = RelMetadataQuery.instance();
final ConversionData conversionData = getConversionData(planner);
final Convention fromConvention = rel.getConvention();
List<List<Convention>> conversionPaths =
conversionData.getPaths(fromConvention, toConvention);
loop:
for (List<Convention> conversionPath : conversionPaths) {
assert conversionPath.get(0) == fromConvention;
assert conversionPath.get(conversionPath.size() - 1)
== toConvention;
RelNode converted = rel;
Convention previous = null;
for (Convention arc : conversionPath) {
if (planner.getCost(converted, mq).isInfinite()
&& !allowInfiniteCostConverters) {
continue loop;
}
if (previous != null) {
converted =
changeConvention(
converted, previous, arc,
conversionData.mapArcToConverterRule);
if (converted == null) {
throw Util.newInternal("Converter from " + previous
+ " to " + arc
+ " guaranteed that it could convert any relexp");
}
}
previous = arc;
}
return converted;
}
return null;
}
/**
* Tries to convert a relational expression to the target convention of an
* arc.
*/
private RelNode changeConvention(
RelNode rel,
Convention source,
Convention target,
final Multimap<Pair<Convention, Convention>, ConverterRule>
mapArcToConverterRule) {
assert source == rel.getConvention();
// Try to apply each converter rule for this arc's source/target calling
// conventions.
final Pair<Convention, Convention> key = Pair.of(source, target);
for (ConverterRule rule : mapArcToConverterRule.get(key)) {
assert rule.getInTrait() == source;
assert rule.getOutTrait() == target;
RelNode converted = rule.convert(rel);
if (converted != null) {
return converted;
}
}
return null;
}
public boolean canConvert(
RelOptPlanner planner,
Convention fromConvention,
Convention toConvention) {
ConversionData conversionData = getConversionData(planner);
return fromConvention.canConvertConvention(toConvention)
|| conversionData.getShortestPath(fromConvention, toConvention) != null;
}
private ConversionData getConversionData(RelOptPlanner planner) {
ConversionData conversionData = plannerConversionMap.get(planner);
if (conversionData == null) {
// Create new, empty ConversionData
conversionData = new ConversionData();
plannerConversionMap.put(planner, conversionData);
}
return conversionData;
}
//~ Inner Classes ----------------------------------------------------------
/** Workspace for converting from one convention to another. */
private static final class ConversionData {
final DirectedGraph<Convention, DefaultEdge> conversionGraph =
DefaultDirectedGraph.create();
/**
* For a given source/target convention, there may be several possible
* conversion rules. Maps {@link DefaultEdge} to a
* collection of {@link ConverterRule} objects.
*/
final Multimap<Pair<Convention, Convention>, ConverterRule>
mapArcToConverterRule =
HashMultimap.create();
private Graphs.FrozenGraph<Convention, DefaultEdge> pathMap;
public List<List<Convention>> getPaths(
Convention fromConvention,
Convention toConvention) {
return getPathMap().getPaths(fromConvention, toConvention);
}
private Graphs.FrozenGraph<Convention, DefaultEdge> getPathMap() {
if (pathMap == null) {
pathMap = Graphs.makeImmutable(conversionGraph);
}
return pathMap;
}
public List<Convention> getShortestPath(
Convention fromConvention,
Convention toConvention) {
return getPathMap().getShortestPath(fromConvention, toConvention);
}
}
}
// End ConventionTraitDef.java