All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.plan.volcano.RuleQueue Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.com.liance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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.plan.RelOptRuleOperand;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.rules.SubstitutionRule;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.org.apache.calcite.util.trace.CalciteTrace;

import com.hazelcast.com.google.com.hazelcast.com.on.collect.HashMultimap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableSet;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Multimap;

import com.hazelcast.org.slf4j.Logger;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * Priority queue of relexps whose rules have not been called, and rule-matches
 * which have not yet been acted upon.
 */
class RuleQueue {
  //~ Static fields/initializers ---------------------------------------------

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

  private static final Set ALL_RULES = ImmutableSet.of("");

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

  /**
   * Map of {@link VolcanoPlannerPhase} to a list of rule-matches. Initially,
   * there is an empty {@link PhaseMatchList} for each planner phase. As the
   * planner invokes {@link #addMatch(VolcanoRuleMatch)} the rule-match is
   * added to the appropriate PhaseMatchList(s). As the planner com.hazelcast.com.letes
   * phases, the matching entry is removed from this list to avoid unused
   * work.
   */
  final Map matchListMap =
      new EnumMap<>(VolcanoPlannerPhase.class);

  private final VolcanoPlanner planner;

  /**
   * Maps a {@link VolcanoPlannerPhase} to a set of rule descriptions. Named rules
   * may be invoked in their corresponding phase.
   *
   * 

See {@link VolcanoPlannerPhaseRuleMappingInitializer} for more * information regarding the contents of this Map and how it is initialized. */ private final Map> phaseRuleMapping; //~ Constructors ----------------------------------------------------------- RuleQueue(VolcanoPlanner planner) { this.planner = planner; phaseRuleMapping = new EnumMap<>(VolcanoPlannerPhase.class); // init empty sets for all phases for (VolcanoPlannerPhase phase : VolcanoPlannerPhase.values()) { phaseRuleMapping.put(phase, new HashSet<>()); } // configure phases planner.getPhaseRuleMappingInitializer().initialize(phaseRuleMapping); for (VolcanoPlannerPhase phase : VolcanoPlannerPhase.values()) { // empty phases get converted to "all rules" if (phaseRuleMapping.get(phase).isEmpty()) { phaseRuleMapping.put(phase, ALL_RULES); } // create a match list data structure for each phase PhaseMatchList matchList = new PhaseMatchList(phase); matchListMap.put(phase, matchList); } } //~ Methods ---------------------------------------------------------------- /** * Clear internal data structure for this rule queue. */ public void clear() { for (PhaseMatchList matchList : matchListMap.values()) { matchList.clear(); } } /** * Removes the {@link PhaseMatchList rule-match list} for the given planner * phase. */ public void phaseCompleted(VolcanoPlannerPhase phase) { matchListMap.get(phase).clear(); } /** * Adds a rule match. The rule-matches are automatically added to all * existing {@link PhaseMatchList per-phase rule-match lists} which allow * the rule referenced by the match. */ void addMatch(VolcanoRuleMatch match) { final String matchName = match.toString(); for (PhaseMatchList matchList : matchListMap.values()) { Set phaseRuleSet = phaseRuleMapping.get(matchList.phase); if (phaseRuleSet != ALL_RULES) { String ruleDescription = match.getRule().toString(); if (!phaseRuleSet.contains(ruleDescription)) { continue; } } if (!matchList.names.add(matchName)) { // Identical match has already been added. continue; } LOGGER.trace("{} Rule-match queued: {}", matchList.phase.toString(), matchName); matchList.offer(match); matchList.matchMap.put( planner.getSubset(match.rels[0]), match); } } /** * Removes the rule match from the head of match list, and returns it. * *

Returns {@code null} if there are no more matches.

* *

Note that the VolcanoPlanner may still decide to reject rule matches * which have become invalid, say if one of their operands belongs to an * obsolete set or has importance=0. * * @throws java.lang.AssertionError if this method is called with a phase * previously marked as com.hazelcast.com.leted via * {@link #phaseCompleted(VolcanoPlannerPhase)}. */ VolcanoRuleMatch popMatch(VolcanoPlannerPhase phase) { dumpPlannerState(); PhaseMatchList phaseMatchList = matchListMap.get(phase); if (phaseMatchList == null) { throw new AssertionError("Used match list for phase " + phase + " after phase com.hazelcast.com.lete"); } VolcanoRuleMatch match; for (;;) { if (phaseMatchList.size() == 0) { return null; } dumpRuleQueue(phaseMatchList); match = phaseMatchList.poll(); if (skipMatch(match)) { LOGGER.debug("Skip match: {}", match); } else { break; } } // If sets have merged since the rule match was enqueued, the match // may not be removed from the matchMap because the subset may have // changed, it is OK to leave it since the matchMap will be cleared // at the end. phaseMatchList.matchMap.remove( planner.getSubset(match.rels[0]), match); LOGGER.debug("Pop match: {}", match); return match; } /** * Dumps rules queue to the logger when debug level is set to {@code TRACE}. */ private void dumpRuleQueue(PhaseMatchList phaseMatchList) { if (LOGGER.isTraceEnabled()) { StringBuilder b = new StringBuilder(); b.append("Rule queue:"); for (VolcanoRuleMatch rule : phaseMatchList.preQueue) { b.append("\n"); b.append(rule); } for (VolcanoRuleMatch rule : phaseMatchList.queue) { b.append("\n"); b.append(rule); } LOGGER.trace(b.toString()); } } /** * Dumps planner's state to the logger when debug level is set to {@code TRACE}. */ private void dumpPlannerState() { if (LOGGER.isTraceEnabled()) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); planner.dump(pw); pw.flush(); LOGGER.trace(sw.toString()); planner.getRoot().getCluster().invalidateMetadataQuery(); } } /** Returns whether to skip a match. This happens if any of the * {@link RelNode}s have importance zero. */ private boolean skipMatch(VolcanoRuleMatch match) { for (RelNode rel : match.rels) { if (planner.prunedNodes.contains(rel)) { return true; } } // If the same subset appears more than once along any path from root // operand to a leaf operand, we have matched a cycle. A relational // expression that consumes its own output can never be implemented, and // furthermore, if we fire rules on it we may generate lots of garbage. // For example, if // Project(A, X = X + 0) // is in the same subset as A, then we would generate // Project(A, X = X + 0 + 0) // Project(A, X = X + 0 + 0 + 0) // also in the same subset. They are valid but useless. final Deque subsets = new ArrayDeque<>(); try { checkDuplicateSubsets(subsets, match.rule.getOperand(), match.rels); } catch (Util.FoundOne e) { return true; } return false; } /** Recursively checks whether there are any duplicate subsets along any path * from root of the operand tree to one of the leaves. * *

It is OK for a match to have duplicate subsets if they are not on the * same path. For example, * *

   *   Join
   *  /   \
   * X     X
   * 
* *

is a valid match. * * @throws com.hazelcast.org.apache.calcite.util.Util.FoundOne on match */ private void checkDuplicateSubsets(Deque subsets, RelOptRuleOperand operand, RelNode[] rels) { final RelSubset subset = planner.getSubset(rels[operand.ordinalInRule]); if (subsets.contains(subset)) { throw Util.FoundOne.NULL; } if (!operand.getChildOperands().isEmpty()) { subsets.push(subset); for (RelOptRuleOperand childOperand : operand.getChildOperands()) { checkDuplicateSubsets(subsets, childOperand, rels); } final RelSubset x = subsets.pop(); assert x == subset; } } //~ Inner Classes ---------------------------------------------------------- /** * PhaseMatchList represents a set of {@link VolcanoRuleMatch rule-matches} * for a particular * {@link VolcanoPlannerPhase phase of the planner's execution}. */ private static class PhaseMatchList { /** * The VolcanoPlannerPhase that this PhaseMatchList is used in. */ final VolcanoPlannerPhase phase; /** * Rule match queue for SubstitutionRule */ private final Queue preQueue = new LinkedList<>(); /** * Current list of VolcanoRuleMatches for this phase. New rule-matches * are appended to the end of this queue. * The rules are not sorted in any way. */ private final Queue queue = new LinkedList<>(); /** * A set of rule-match names contained in {@link #queue}. Allows fast * detection of duplicate rule-matches. */ final Set names = new HashSet<>(); /** * Multi-map of RelSubset to VolcanoRuleMatches. */ final Multimap matchMap = HashMultimap.create(); PhaseMatchList(VolcanoPlannerPhase phase) { this.phase = phase; } int size() { return preQueue.size() + queue.size(); } VolcanoRuleMatch poll() { VolcanoRuleMatch match = preQueue.poll(); if (match == null) { match = queue.poll(); } return match; } void offer(VolcanoRuleMatch match) { if (match.getRule() instanceof SubstitutionRule) { preQueue.offer(match); } else { queue.offer(match); } } void clear() { preQueue.clear(); queue.clear(); names.clear(); matchMap.clear(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy