org.apache.calcite.plan.volcano.RelSubset.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.calcite.plan.volcano.RelSubset.java

Source

/*
 * 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.volcano;

import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.linq4j.function.Predicate1;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptListener;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTrait;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.AbstractRelNode;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;

import com.google.common.collect.Iterables;

import org.slf4j.Logger;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Subset of an equivalence class where all relational expressions have the
 * same physical properties.
 *
 * <p>Physical properties are instances of the {@link RelTraitSet}, and consist
 * of traits such as calling convention and collation (sort-order).
 *
 * <p>For some traits, a relational expression can have more than one instance.
 * For example, R can be sorted on both [X] and [Y, Z]. In which case, R would
 * belong to the sub-sets for [X] and [Y, Z]; and also the leading edges [Y] and
 * [].
 *
 * @see RelNode
 * @see RelSet
 * @see RelTrait
 */
public class RelSubset extends AbstractRelNode {
    //~ Static fields/initializers ---------------------------------------------

    private static final Logger LOGGER = CalciteTrace.getPlannerTracer();

    //~ Instance fields --------------------------------------------------------

    /**
     * cost of best known plan (it may have improved since)
     */
    RelOptCost bestCost;

    /**
     * The set this subset belongs to.
     */
    final RelSet set;

    /**
     * best known plan
     */
    RelNode best;

    /**
     * Timestamp for metadata validity
     */
    long timestamp;

    /**
     * Flag indicating whether this RelSubset's importance was artificially
     * boosted.
     */
    boolean boosted;

    //~ Constructors -----------------------------------------------------------

    RelSubset(RelOptCluster cluster, RelSet set, RelTraitSet traits) {
        super(cluster, traits);
        this.set = set;
        this.boosted = false;
        assert traits.allSimple();
        computeBestCost(cluster.getPlanner());
        recomputeDigest();
    }

    //~ Methods ----------------------------------------------------------------

    /**
     * Computes the best {@link RelNode} in this subset.
     *
     * <p>Only necessary when a subset is created in a set that has subsets that
     * subsume it. Rationale:</p>
     *
     * <ol>
     * <li>If the are no subsuming subsets, the subset is initially empty.</li>
     * <li>After creation, {@code best} and {@code bestCost} are maintained
     *    incrementally by {@link #propagateCostImprovements0} and
     *    {@link RelSet#mergeWith(VolcanoPlanner, RelSet)}.</li>
     * </ol>
     */
    private void computeBestCost(RelOptPlanner planner) {
        bestCost = planner.getCostFactory().makeInfiniteCost();
        final RelMetadataQuery mq = RelMetadataQuery.instance();
        for (RelNode rel : getRels()) {
            final RelOptCost cost = planner.getCost(rel, mq);
            if (cost.isLt(bestCost)) {
                bestCost = cost;
                best = rel;
            }
        }
    }

    public RelNode getBest() {
        return best;
    }

    public RelNode getOriginal() {
        return set.rel;
    }

    public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
        throw new UnsupportedOperationException();
    }

    public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
        return planner.getCostFactory().makeZeroCost();
    }

    public double estimateRowCount(RelMetadataQuery mq) {
        if (best != null) {
            return mq.getRowCount(best);
        } else {
            return mq.getRowCount(set.rel);
        }
    }

    @Override
    public void explain(RelWriter pw) {
        // Not a typical implementation of "explain". We don't gather terms &
        // values to be printed later. We actually do the work.
        String s = getDescription();
        pw.item("subset", s);
        final AbstractRelNode input = (AbstractRelNode) Iterables.getFirst(getRels(), null);
        if (input == null) {
            return;
        }
        input.explainTerms(pw);
        pw.done(input);
    }

    @Override
    protected String computeDigest() {
        StringBuilder digest = new StringBuilder("Subset#");
        digest.append(set.id);
        for (RelTrait trait : traitSet) {
            digest.append('.').append(trait);
        }
        return digest.toString();
    }

    @Override
    protected RelDataType deriveRowType() {
        return set.rel.getRowType();
    }

    /**
     * Returns the collection of RelNodes one of whose inputs is in this
     * subset.
     */
    Set<RelNode> getParents() {
        final Set<RelNode> list = new LinkedHashSet<>();
        for (RelNode parent : set.getParentRels()) {
            for (RelSubset rel : inputSubsets(parent)) {
                if (rel.set == set && traitSet.satisfies(rel.getTraitSet())) {
                    list.add(parent);
                }
            }
        }
        return list;
    }

    /**
     * Returns the collection of distinct subsets that contain a RelNode one
     * of whose inputs is in this subset.
     */
    Set<RelSubset> getParentSubsets(VolcanoPlanner planner) {
        final Set<RelSubset> list = new LinkedHashSet<>();
        for (RelNode parent : set.getParentRels()) {
            for (RelSubset rel : inputSubsets(parent)) {
                if (rel.set == set && rel.getTraitSet().equals(traitSet)) {
                    list.add(planner.getSubset(parent));
                }
            }
        }
        return list;
    }

    private static List<RelSubset> inputSubsets(RelNode parent) {
        //noinspection unchecked
        return (List<RelSubset>) (List) parent.getInputs();
    }

    /**
     * Returns a list of relational expressions one of whose children is this
     * subset. The elements of the list are distinct.
     */
    public Collection<RelNode> getParentRels() {
        final Set<RelNode> list = new LinkedHashSet<>();
        parentLoop: for (RelNode parent : set.getParentRels()) {
            for (RelSubset rel : inputSubsets(parent)) {
                if (rel.set == set && traitSet.satisfies(rel.getTraitSet())) {
                    list.add(parent);
                    continue parentLoop;
                }
            }
        }
        return list;
    }

    RelSet getSet() {
        return set;
    }

    /**
     * Adds expression <code>rel</code> to this subset.
     */
    void add(RelNode rel) {
        if (set.rels.contains(rel)) {
            return;
        }

        VolcanoPlanner planner = (VolcanoPlanner) rel.getCluster().getPlanner();
        if (planner.listener != null) {
            RelOptListener.RelEquivalenceEvent event = new RelOptListener.RelEquivalenceEvent(planner, rel, this,
                    true);
            planner.listener.relEquivalenceFound(event);
        }

        // If this isn't the first rel in the set, it must have compatible
        // row type.
        if (set.rel != null) {
            RelOptUtil.equal("rowtype of new rel", rel.getRowType(), "rowtype of set", getRowType(), Litmus.THROW);
        }
        set.addInternal(rel);
        Set<CorrelationId> variablesSet = RelOptUtil.getVariablesSet(rel);
        Set<CorrelationId> variablesStopped = rel.getVariablesSet();
        if (false) {
            Set<CorrelationId> variablesPropagated = Util.minus(variablesSet, variablesStopped);
            assert set.variablesPropagated.containsAll(variablesPropagated);
            Set<CorrelationId> variablesUsed = RelOptUtil.getVariablesUsed(rel);
            assert set.variablesUsed.containsAll(variablesUsed);
        }
    }

    /**
     * Recursively builds a tree consisting of the cheapest plan at each node.
     */
    RelNode buildCheapestPlan(VolcanoPlanner planner) {
        CheapestPlanReplacer replacer = new CheapestPlanReplacer(planner);
        final RelNode cheapest = replacer.visit(this, -1, null);

        if (planner.listener != null) {
            RelOptListener.RelChosenEvent event = new RelOptListener.RelChosenEvent(planner, null);
            planner.listener.relChosen(event);
        }

        return cheapest;
    }

    /**
     * Checks whether a relexp has made its subset cheaper, and if it so,
     * recursively checks whether that subset's parents have gotten cheaper.
     *
     * @param planner   Planner
     * @param mq        Metadata query
     * @param rel       Relational expression whose cost has improved
     * @param activeSet Set of active subsets, for cycle detection
     */
    void propagateCostImprovements(VolcanoPlanner planner, RelMetadataQuery mq, RelNode rel,
            Set<RelSubset> activeSet) {
        for (RelSubset subset : set.subsets) {
            if (rel.getTraitSet().satisfies(subset.traitSet)) {
                subset.propagateCostImprovements0(planner, mq, rel, activeSet);
            }
        }
    }

    void propagateCostImprovements0(VolcanoPlanner planner, RelMetadataQuery mq, RelNode rel,
            Set<RelSubset> activeSet) {
        ++timestamp;

        if (!activeSet.add(this)) {
            // This subset is already in the chain being propagated to. This
            // means that the graph is cyclic, and therefore the cost of this
            // relational expression - not this subset - must be infinite.
            LOGGER.trace("cyclic: {}", this);
            return;
        }
        try {
            final RelOptCost cost = planner.getCost(rel, mq);
            if (cost.isLt(bestCost)) {
                LOGGER.trace("Subset cost improved: subset [{}] cost was {} now {}", this, bestCost, cost);

                bestCost = cost;
                best = rel;

                // Lower cost means lower importance. Other nodes will change
                // too, but we'll get to them later.
                planner.ruleQueue.recompute(this);
                for (RelNode parent : getParents()) {
                    final RelSubset parentSubset = planner.getSubset(parent);
                    parentSubset.propagateCostImprovements(planner, mq, parent, activeSet);
                }
                planner.checkForSatisfiedConverters(set, rel);
            }
        } finally {
            activeSet.remove(this);
        }
    }

    public void propagateBoostRemoval(VolcanoPlanner planner) {
        planner.ruleQueue.recompute(this);

        if (boosted) {
            boosted = false;

            for (RelSubset parentSubset : getParentSubsets(planner)) {
                parentSubset.propagateBoostRemoval(planner);
            }
        }
    }

    @Override
    public void collectVariablesUsed(Set<CorrelationId> variableSet) {
        variableSet.addAll(set.variablesUsed);
    }

    @Override
    public void collectVariablesSet(Set<CorrelationId> variableSet) {
        variableSet.addAll(set.variablesPropagated);
    }

    /**
     * Returns the rel nodes in this rel subset.  All rels must have the same
     * traits and are logically equivalent.
     *
     * @return all the rels in the subset
     */
    public Iterable<RelNode> getRels() {
        return new Iterable<RelNode>() {
            public Iterator<RelNode> iterator() {
                return Linq4j.asEnumerable(set.rels).where(new Predicate1<RelNode>() {
                    public boolean apply(RelNode v1) {
                        return v1.getTraitSet().satisfies(traitSet);
                    }
                }).iterator();
            }
        };
    }

    /**
     * As {@link #getRels()} but returns a list.
     */
    public List<RelNode> getRelList() {
        final List<RelNode> list = new ArrayList<>();
        for (RelNode rel : set.rels) {
            if (rel.getTraitSet().satisfies(traitSet)) {
                list.add(rel);
            }
        }
        return list;
    }

    //~ Inner Classes ----------------------------------------------------------

    /**
     * Visitor which walks over a tree of {@link RelSet}s, replacing each node
     * with the cheapest implementation of the expression.
     */
    static class CheapestPlanReplacer {
        VolcanoPlanner planner;

        CheapestPlanReplacer(VolcanoPlanner planner) {
            super();
            this.planner = planner;
        }

        public RelNode visit(RelNode p, int ordinal, RelNode parent) {
            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.println("Node [" + subset.getDescription() + "] could not be implemented; planner state:\n");
                    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.listener != null) {
                    RelOptListener.RelChosenEvent event = new RelOptListener.RelChosenEvent(planner, p);
                    planner.listener.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));
            }
            return p;
        }
    }
}

// End RelSubset.java