com.hazelcast.org.apache.calcite.plan.RelRule Maven / Gradle / Ivy
/*
* 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 com.hazelcast.org.apache.calcite.plan;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.core.RelFactories;
import com.hazelcast.org.apache.calcite.tools.RelBuilderFactory;
import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import org.immutables.value.Value;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Rule that is parameterized via a configuration.
*
* Eventually (before Calcite version 2.0), this class will replace
* {@link RelOptRule}. Constructors of {@code RelOptRule} are deprecated, so new
* rule classes should extend {@code RelRule}, not {@code RelOptRule}.
* Next, we will deprecate {@code RelOptRule}, so that variables that reference
* rules will be of type {@code RelRule}.
*
*
Guidelines for writing rules
*
*
1. If your rule is a sub-class of
* {@link com.hazelcast.org.apache.calcite.rel.convert.ConverterRule}
* and does not need any extra properties,
* there's no need to create an {@code interface Config} inside your class.
* In your class, create a constant
* {@code public static final Config DEFAULT_CONFIG}. Goto step 5.
*
*
2. If your rule is not a sub-class of
* {@link com.hazelcast.org.apache.calcite.rel.convert.ConverterRule},
* create an inner {@code interface Config extends RelRule.Config} and
* annotate it with {@code @Value.Immutable}. Note, if your inner class
* is two levels deep (e.g. top-level Rule with Config inside), we recommend
* you annotate the outer class with {@code @Value.Enclosing} which will
* instruct the annotation processor to put your generated value class
* inside a new Immutable outer class. If your rule is three levels deep,
* the best thing to do is give your class a unique name to avoid any
* generated code class name overlaps.
* Implement {@link Config#toRule() toRule} using a {@code default} method:
*
*
*
* @Override default CsvProjectTableScanRule toRule() {
* return new CsvProjectTableScanRule(this);
* }
*
*
*
* 3. For each configuration property, create a pair of methods in your
* {@code Config} interface. For example, for a property {@code foo} of type
* {@code int}, create methods {@code foo} and {@code withFoo}:
*
*
* /** Returns foo. */
* int foo();
*
* /** Sets {@link #foo}. */
* Config withFoo(int x);
*
*
* 4. In your {@code Config} interface, create a {@code DEFAULT} constant
* that represents the most typical configuration of your rule. This default
* will leverage the Immutables class generated by the Annotation Processor
* based on the annotation you provided above. For example,
* {@code CsvProjectTableScanRule.Config} has the following:
*
*
* Config DEFAULT = ImmutableCsvProjectTableScanRule.Config.builder()
* .withOperandSupplier(b0 ->
* b0.operand(LogicalProject.class).oneInput(b1 ->
* b1.operand(CsvTableScan.class).noInputs()))
* .build();
*
*
* 5. Do not create an {@code INSTANCE} constant inside your rule.
* Instead, create a named instance of your rule, with default configuration,
* in a holder class. The holder class must not be a sub-class of
* {@code RelOptRule} (otherwise cyclic class-loading issues may arise).
* Generally it will be called XxxRules
, for example
* {@code CsvRules}. The rule instance is named after your rule, and is based
* on the default config ({@code Config.DEFAULT}, or {@code DEFAULT_CONFIG} for
* converter rules):
*
*
* /** Rule that matches a {@code Project} on a
* * {@code CsvTableScan} and pushes down projects if possible. */
* public static final CsvProjectTableScanRule PROJECT_SCAN =
* CsvProjectTableScanRule.Config.DEFAULT.toRule();
*
*
* @param Configuration type
*/
public abstract class RelRule extends RelOptRule {
public final C config;
/** Creates a RelRule. */
protected RelRule(C config) {
super(OperandBuilderImpl.operand(config.operandSupplier()),
config.relBuilderFactory(), config.description());
this.config = config;
}
/** Rule configuration. */
public interface Config {
/** Creates a rule that uses this configuration. Sub-class must override. */
RelOptRule toRule();
/** Casts this configuration to another type, usually a sub-class. */
default T as(Class class_) {
if (class_.isAssignableFrom(this.getClass())) {
return class_.cast(this);
} else {
throw new UnsupportedOperationException(
String.format(Locale.ROOT,
"The current config of type %s is not an instance of %s.",
this.getClass(),
class_));
}
}
/** The factory that is used to create a
* {@link com.hazelcast.org.apache.calcite.tools.RelBuilder} during rule invocations. */
@Value.Default default RelBuilderFactory relBuilderFactory() {
return RelFactories.LOGICAL_BUILDER;
}
/** Sets {@link #relBuilderFactory()}. */
Config withRelBuilderFactory(RelBuilderFactory factory);
/** Description of the rule instance. */
// CALCITE-4831: remove the second nullable annotation once immutables/#1261 is fixed
@javax.annotation.Nullable @Nullable String description();
/** Sets {@link #description()}. */
Config withDescription(@Nullable String description);
/** Creates the operands for the rule instance. */
@Value.Default default OperandTransform operandSupplier() {
return s -> {
throw new IllegalArgumentException("Rules must have at least one "
+ "operand. Call Config.withOperandSupplier to specify them.");
};
}
/** Sets {@link #operandSupplier()}. */
Config withOperandSupplier(OperandTransform transform);
}
/** Function that creates an operand.
*
* @see Config#withOperandSupplier(OperandTransform) */
@FunctionalInterface
public interface OperandTransform extends Function {
}
/** Callback to create an operand.
*
* @see OperandTransform */
public interface OperandBuilder {
/** Starts building an operand by specifying its class.
* Call further methods on the returned {@link OperandDetailBuilder} to
* complete the operand. */
OperandDetailBuilder operand(Class relClass);
/** Supplies an operand that has been built manually. */
Done exactly(RelOptRuleOperand operand);
}
/** Indicates that an operand is complete.
*
* @see OperandTransform */
public interface Done {
}
/** Add details about an operand, such as its inputs.
*
* @param Type of relational expression */
public interface OperandDetailBuilder {
/** Sets a trait of this operand. */
OperandDetailBuilder trait(RelTrait trait);
/** Sets the predicate of this operand. */
OperandDetailBuilder predicate(Predicate super R> predicate);
/** Indicates that this operand has a single input. */
Done oneInput(OperandTransform transform);
/** Indicates that this operand has several inputs. */
Done inputs(OperandTransform... transforms);
/** Indicates that this operand has several inputs, unordered. */
Done unorderedInputs(OperandTransform... transforms);
/** Indicates that this operand takes any number or type of inputs. */
Done anyInputs();
/** Indicates that this operand takes no inputs. */
Done noInputs();
/** Indicates that this operand converts a relational expression to
* another trait. */
Done convert(RelTrait in);
}
/** Implementation of {@link OperandBuilder}. */
private static class OperandBuilderImpl implements OperandBuilder {
final List operands = new ArrayList<>();
static RelOptRuleOperand operand(OperandTransform transform) {
final OperandBuilderImpl b = new OperandBuilderImpl();
final Done done = transform.apply(b);
Objects.requireNonNull(done, "done");
if (b.operands.size() != 1) {
throw new IllegalArgumentException("operand supplier must call one of "
+ "the following methods: operand or exactly");
}
return b.operands.get(0);
}
@Override public OperandDetailBuilder operand(Class relClass) {
return new OperandDetailBuilderImpl<>(this, relClass);
}
@Override public Done exactly(RelOptRuleOperand operand) {
operands.add(operand);
return DoneImpl.INSTANCE;
}
}
/** Implementation of {@link OperandDetailBuilder}.
*
* @param Type of relational expression */
private static class OperandDetailBuilderImpl
implements OperandDetailBuilder {
private final OperandBuilderImpl parent;
private final Class relClass;
final OperandBuilderImpl inputBuilder = new OperandBuilderImpl();
private @Nullable RelTrait trait;
private Predicate super R> predicate = r -> true;
OperandDetailBuilderImpl(OperandBuilderImpl parent, Class relClass) {
this.parent = Objects.requireNonNull(parent, "parent");
this.relClass = Objects.requireNonNull(relClass, "relClass");
}
@Override public OperandDetailBuilderImpl trait(RelTrait trait) {
this.trait = Objects.requireNonNull(trait, "trait");
return this;
}
@Override public OperandDetailBuilderImpl predicate(Predicate super R> predicate) {
this.predicate = predicate;
return this;
}
/** Indicates that there are no more inputs. */
Done done(RelOptRuleOperandChildPolicy childPolicy) {
parent.operands.add(
new RelOptRuleOperand(relClass, trait, predicate, childPolicy,
ImmutableList.copyOf(inputBuilder.operands)));
return DoneImpl.INSTANCE;
}
@Override public Done convert(RelTrait in) {
parent.operands.add(
new ConverterRelOptRuleOperand(relClass, in, predicate));
return DoneImpl.INSTANCE;
}
@Override public Done noInputs() {
return done(RelOptRuleOperandChildPolicy.LEAF);
}
@Override public Done anyInputs() {
return done(RelOptRuleOperandChildPolicy.ANY);
}
@Override public Done oneInput(OperandTransform transform) {
final Done done = transform.apply(inputBuilder);
Objects.requireNonNull(done, "done");
return done(RelOptRuleOperandChildPolicy.SOME);
}
@Override public Done inputs(OperandTransform... transforms) {
for (OperandTransform transform : transforms) {
final Done done = transform.apply(inputBuilder);
Objects.requireNonNull(done, "done");
}
return done(RelOptRuleOperandChildPolicy.SOME);
}
@Override public Done unorderedInputs(OperandTransform... transforms) {
for (OperandTransform transform : transforms) {
final Done done = transform.apply(inputBuilder);
Objects.requireNonNull(done, "done");
}
return done(RelOptRuleOperandChildPolicy.UNORDERED);
}
}
/** Singleton instance of {@link Done}. */
private enum DoneImpl implements Done {
INSTANCE
}
/** Callback interface that helps you avoid creating sub-classes of
* {@link RelRule} that differ only in implementations of
* {@link #onMatch(RelOptRuleCall)} method.
*
* @param Rule type */
public interface MatchHandler
extends BiConsumer {
}
}