HintStrategy.java

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

import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.rel.convert.ConverterRule;

import com.google.common.collect.ImmutableSet;

import org.checkerframework.checker.nullness.qual.Nullable;

import static java.util.Objects.requireNonNull;

/**
 * Represents a hint strategy entry of {@link HintStrategyTable}.
 *
 * <p>A {@code HintStrategy} defines:
 *
 * <ul>
 *   <li>{@link HintPredicate}: tests whether a hint should apply to
 *   a relational expression;</li>
 *   <li>{@link HintOptionChecker}: validates the hint options;</li>
 *   <li>{@code excludedRules}: rules to exclude when a relational expression
 *   is going to apply a planner rule;</li>
 *   <li>{@code converterRules}: fallback rules to apply when there are
 *   no proper implementations after excluding the {@code excludedRules}.</li>
 * </ul>
 *
 * <p>The {@link HintPredicate} is required, all the other items are optional.
 *
 * <p>{@link HintStrategy} is immutable.
 */
public class HintStrategy {
  //~ Instance fields --------------------------------------------------------

  public final HintPredicate predicate;
  public final @Nullable HintOptionChecker hintOptionChecker;
  public final ImmutableSet<RelOptRule> excludedRules;
  public final ImmutableSet<ConverterRule> converterRules;

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

  private HintStrategy(
      HintPredicate predicate,
      @Nullable HintOptionChecker hintOptionChecker,
      ImmutableSet<RelOptRule> excludedRules,
      ImmutableSet<ConverterRule> converterRules) {
    this.predicate = predicate;
    this.hintOptionChecker = hintOptionChecker;
    this.excludedRules = excludedRules;
    this.converterRules = converterRules;
  }

  /**
   * Returns a {@link HintStrategy} builder with given hint predicate.
   *
   * @param hintPredicate hint predicate
   * @return {@link Builder} instance
   */
  public static Builder builder(HintPredicate hintPredicate) {
    return new Builder(hintPredicate);
  }

  //~ Inner Class ------------------------------------------------------------

  /** Builder for {@link HintStrategy}. */
  public static class Builder {
    private final HintPredicate predicate;
    private @Nullable HintOptionChecker optionChecker;
    private ImmutableSet<RelOptRule> excludedRules;
    private ImmutableSet<ConverterRule> converterRules;

    private Builder(HintPredicate predicate) {
      this.predicate = requireNonNull(predicate, "predicate");
      this.excludedRules = ImmutableSet.of();
      this.converterRules = ImmutableSet.of();
    }

    /** Registers a hint option checker to validate the hint options. */
    public Builder optionChecker(HintOptionChecker optionChecker) {
      this.optionChecker = requireNonNull(optionChecker, "optionChecker");
      return this;
    }

    /**
     * Registers an array of rules to exclude during the
     * {@link org.apache.calcite.plan.RelOptPlanner} planning.
     *
     * <p>The desired converter rules work together with the excluded rules.
     * We have no validation here but they expect to have the same
     * function(semantic equivalent).
     *
     * <p>A rule fire cancels if:
     *
     * <ol>
     *   <li>The registered {@link #excludedRules} contains the rule</li>
     *   <li>And the desired converter rules conversion is not possible
     *   for the rule matched root node</li>
     * </ol>
     *
     * @param rules excluded rules
     */
    public Builder excludedRules(RelOptRule... rules) {
      this.excludedRules = ImmutableSet.copyOf(rules);
      return this;
    }

    /**
     * Registers an array of desired converter rules during the
     * {@link org.apache.calcite.plan.RelOptPlanner} planning.
     *
     * <p>The desired converter rules work together with the excluded rules.
     * We have no validation here but they expect to have the same
     * function(semantic equivalent).
     *
     * <p>A rule fire cancels if:
     *
     * <ol>
     *   <li>The registered {@link #excludedRules} contains the rule</li>
     *   <li>And the desired converter rules conversion is not possible
     *   for the rule matched root node</li>
     * </ol>
     *
     * <p>If no converter rules are specified, we assume the conversion is possible.
     *
     * @param rules desired converter rules
     */
    public Builder converterRules(ConverterRule... rules) {
      this.converterRules = ImmutableSet.copyOf(rules);
      return this;
    }

    public HintStrategy build() {
      return new HintStrategy(predicate, optionChecker, excludedRules, converterRules);
    }
  }
}