blob: 31f7e182a8fab728e8b966a31dba6e4cdbf50c40 [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.rel.rules;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.RelFactories.ProjectFactory;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.util.Permutation;
import org.immutables.value.Value;
import java.util.List;
/**
* ProjectMergeRule merges a {@link org.apache.calcite.rel.core.Project} into
* another {@link org.apache.calcite.rel.core.Project},
* provided the projects aren't projecting identical sets of input references.
*
* @see CoreRules#PROJECT_MERGE
*/
@Value.Enclosing
public class ProjectMergeRule
extends RelRule<ProjectMergeRule.Config>
implements TransformationRule {
/** Default amount by which complexity is allowed to increase.
*
* @see Config#bloat() */
public static final int DEFAULT_BLOAT = 100;
/** Creates a ProjectMergeRule. */
protected ProjectMergeRule(Config config) {
super(config);
}
@Deprecated // to be removed before 2.0
public ProjectMergeRule(boolean force, int bloat,
RelBuilderFactory relBuilderFactory) {
this(CoreRules.PROJECT_MERGE.config.withRelBuilderFactory(relBuilderFactory)
.as(Config.class)
.withForce(force)
.withBloat(bloat));
}
@Deprecated // to be removed before 2.0
public ProjectMergeRule(boolean force, RelBuilderFactory relBuilderFactory) {
this(CoreRules.PROJECT_MERGE.config.withRelBuilderFactory(relBuilderFactory)
.as(Config.class)
.withForce(force));
}
@Deprecated // to be removed before 2.0
public ProjectMergeRule(boolean force, ProjectFactory projectFactory) {
this(CoreRules.PROJECT_MERGE.config.withRelBuilderFactory(RelBuilder.proto(projectFactory))
.as(Config.class)
.withForce(force));
}
//~ Methods ----------------------------------------------------------------
@Override public boolean matches(RelOptRuleCall call) {
final Project topProject = call.rel(0);
final Project bottomProject = call.rel(1);
return topProject.getConvention() == bottomProject.getConvention();
}
@Override public void onMatch(RelOptRuleCall call) {
final Project topProject = call.rel(0);
final Project bottomProject = call.rel(1);
final RelBuilder relBuilder = call.builder();
// If one or both projects are permutations, short-circuit the complex logic
// of building a RexProgram.
final Permutation topPermutation = topProject.getPermutation();
if (topPermutation != null) {
if (topPermutation.isIdentity()) {
// Let ProjectRemoveRule handle this.
return;
}
final Permutation bottomPermutation = bottomProject.getPermutation();
if (bottomPermutation != null) {
if (bottomPermutation.isIdentity()) {
// Let ProjectRemoveRule handle this.
return;
}
final Permutation product = topPermutation.product(bottomPermutation);
relBuilder.push(bottomProject.getInput());
relBuilder.project(relBuilder.fields(product),
topProject.getRowType().getFieldNames());
call.transformTo(relBuilder.build());
return;
}
}
// If we're not in force mode and the two projects reference identical
// inputs, then return and let ProjectRemoveRule replace the projects.
if (!config.force()) {
if (RexUtil.isIdentity(topProject.getProjects(),
topProject.getInput().getRowType())) {
return;
}
}
final List<RexNode> newProjects =
RelOptUtil.pushPastProjectUnlessBloat(topProject.getProjects(),
bottomProject, config.bloat());
if (newProjects == null) {
// Merged projects are significantly more complex. Do not merge.
return;
}
final RelNode input = bottomProject.getInput();
if (RexUtil.isIdentity(newProjects, input.getRowType())) {
if (config.force()
|| input.getRowType().getFieldNames()
.equals(topProject.getRowType().getFieldNames())) {
call.transformTo(input);
return;
}
}
// replace the two projects with a combined projection
relBuilder.push(bottomProject.getInput());
relBuilder.project(newProjects, topProject.getRowType().getFieldNames());
call.transformTo(relBuilder.build());
}
/** Rule configuration. */
@Value.Immutable
public interface Config extends RelRule.Config {
Config DEFAULT = ImmutableProjectMergeRule.Config.of()
.withOperandFor(Project.class);
@Override default ProjectMergeRule toRule() {
return new ProjectMergeRule(this);
}
/** Limit how much complexity can increase during merging.
* Default is {@link #DEFAULT_BLOAT} (100). */
@Value.Default default int bloat() {
return ProjectMergeRule.DEFAULT_BLOAT;
}
/** Sets {@link #bloat()}. */
Config withBloat(int bloat);
/** Whether to always merge projects, default true. */
@Value.Default default boolean force() {
return true;
}
/** Sets {@link #force()}. */
Config withForce(boolean force);
/** Defines an operand tree for the given classes. */
default Config withOperandFor(Class<? extends Project> projectClass) {
return withOperandSupplier(b0 ->
b0.operand(projectClass).oneInput(b1 ->
b1.operand(projectClass).anyInputs()))
.as(Config.class);
}
}
}