blob: 8260e6293d1f7e9136c79507ab55f451c26c4e6f [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.Join;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.logical.LogicalJoin;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.tools.RelBuilderFactory;
import org.immutables.value.Value;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
/**
* Planner rule that pushes a {@link org.apache.calcite.rel.core.Project}
* past a {@link org.apache.calcite.rel.core.Join}
* by splitting the projection into a projection on top of each child of
* the join.
*
* @see CoreRules#PROJECT_JOIN_TRANSPOSE
*/
@Value.Enclosing
public class ProjectJoinTransposeRule
extends RelRule<ProjectJoinTransposeRule.Config>
implements TransformationRule {
/** Creates a ProjectJoinTransposeRule. */
protected ProjectJoinTransposeRule(Config config) {
super(config);
}
@Deprecated // to be removed before 2.0
public ProjectJoinTransposeRule(
Class<? extends Project> projectClass,
Class<? extends Join> joinClass,
PushProjector.ExprCondition preserveExprCondition,
RelBuilderFactory relBuilderFactory) {
this(Config.DEFAULT
.withRelBuilderFactory(relBuilderFactory)
.as(Config.class)
.withOperandFor(projectClass, joinClass)
.withPreserveExprCondition(preserveExprCondition));
}
//~ Methods ----------------------------------------------------------------
@Override public void onMatch(RelOptRuleCall call) {
final Project origProject = call.rel(0);
final Join join = call.rel(1);
// Normalize the join condition so we don't end up misidentified expanded
// form of IS NOT DISTINCT FROM as PushProject also visit the filter condition
// and push down expressions.
RexNode joinFilter = join.getCondition().accept(new RexShuttle() {
@Override public RexNode visitCall(RexCall rexCall) {
final RexNode node = super.visitCall(rexCall);
if (!(node instanceof RexCall)) {
return node;
}
return RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) node,
call.builder().getRexBuilder());
}
});
// locate all fields referenced in the projection and join condition;
// determine which inputs are referenced in the projection and
// join condition; if all fields are being referenced and there are no
// special expressions, no point in proceeding any further
final PushProjector pushProjector =
new PushProjector(
origProject,
joinFilter,
join,
config.preserveExprCondition(),
call.builder());
if (pushProjector.locateAllRefs()) {
return;
}
// create left and right projections, projecting only those
// fields referenced on each side
final RelNode leftProject =
pushProjector.createProjectRefsAndExprs(
join.getLeft(),
true,
false);
final RelNode rightProject =
pushProjector.createProjectRefsAndExprs(
join.getRight(),
true,
true);
// convert the join condition to reference the projected columns
RexNode newJoinFilter = null;
int[] adjustments = pushProjector.getAdjustments();
if (joinFilter != null) {
List<RelDataTypeField> projectJoinFieldList = new ArrayList<>();
projectJoinFieldList.addAll(
join.getSystemFieldList());
projectJoinFieldList.addAll(
leftProject.getRowType().getFieldList());
projectJoinFieldList.addAll(
rightProject.getRowType().getFieldList());
newJoinFilter =
pushProjector.convertRefsAndExprs(
joinFilter,
projectJoinFieldList,
adjustments);
}
// create a new join with the projected children
final Join newJoin =
join.copy(
join.getTraitSet(),
requireNonNull(newJoinFilter, "newJoinFilter must not be null"),
leftProject,
rightProject,
join.getJoinType(),
join.isSemiJoinDone());
// put the original project on top of the join, converting it to
// reference the modified projection list
final RelNode topProject =
pushProjector.createNewProject(newJoin, adjustments);
call.transformTo(topProject);
}
/** Rule configuration. */
@Value.Immutable(singleton = false)
public interface Config extends RelRule.Config {
Config DEFAULT = ImmutableProjectJoinTransposeRule.Config.builder()
.withPreserveExprCondition(expr -> !(expr instanceof RexOver))
.build()
.withOperandFor(LogicalProject.class, LogicalJoin.class);
@Override default ProjectJoinTransposeRule toRule() {
return new ProjectJoinTransposeRule(this);
}
/** Defines when an expression should not be pushed. */
PushProjector.ExprCondition preserveExprCondition();
/** Sets {@link #preserveExprCondition()}. */
Config withPreserveExprCondition(PushProjector.ExprCondition condition);
/** Defines an operand tree for the given classes. */
default Config withOperandFor(Class<? extends Project> projectClass,
Class<? extends Join> joinClass) {
return withOperandSupplier(b0 ->
b0.operand(projectClass).oneInput(b1 ->
b1.operand(joinClass).anyInputs()))
.as(Config.class);
}
}
}