com.hazelcast.org.apache.calcite.rel.rules.JoinPushThroughJoinRule 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.rel.rules;
import com.hazelcast.org.apache.calcite.plan.RelOptCluster;
import com.hazelcast.org.apache.calcite.plan.RelOptRuleCall;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.plan.RelRule;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.core.Join;
import com.hazelcast.org.apache.calcite.rel.core.JoinRelType;
import com.hazelcast.org.apache.calcite.rel.core.RelFactories.ProjectFactory;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalJoin;
import com.hazelcast.org.apache.calcite.rex.RexBuilder;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexPermuteInputsShuttle;
import com.hazelcast.org.apache.calcite.rex.RexUtil;
import com.hazelcast.org.apache.calcite.tools.RelBuilder;
import com.hazelcast.org.apache.calcite.tools.RelBuilderFactory;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import com.hazelcast.org.apache.calcite.util.mapping.Mappings;
import org.immutables.value.Value;
import java.util.ArrayList;
import java.util.List;
/**
* Rule that pushes the right input of a join into through the left input of
* the join, provided that the left input is also a join.
*
* Thus, {@code (A join B) join C} becomes {@code (A join C) join B}. The
* advantage of applying this rule is that it may be possible to apply
* conditions earlier. For instance,
*
*
* (sales as s join product_class as pc on true)
* join product as p
* on s.product_id = p.product_id
* and p.product_class_id = pc.product_class_id
*
* becomes
*
*
* (sales as s join product as p on s.product_id = p.product_id)
* join product_class as pc
* on p.product_class_id = pc.product_class_id
*
* Before the rule, one join has two conditions and the other has none
* ({@code ON TRUE}). After the rule, each join has one condition.
*/
@Value.Enclosing
public class JoinPushThroughJoinRule
extends RelRule
implements TransformationRule {
/** Instance of the rule that works on logical joins only, and pushes to the
* right. */
public static final JoinPushThroughJoinRule RIGHT = Config.RIGHT.toRule();
/** Instance of the rule that works on logical joins only, and pushes to the
* left. */
public static final JoinPushThroughJoinRule LEFT = Config.LEFT.toRule();
/** Creates a JoinPushThroughJoinRule. */
protected JoinPushThroughJoinRule(Config config) {
super(config);
}
@Deprecated // to be removed before 2.0
public JoinPushThroughJoinRule(String description, boolean right,
Class extends Join> joinClass, RelBuilderFactory relBuilderFactory) {
this(Config.LEFT.withDescription(description)
.withRelBuilderFactory(relBuilderFactory)
.as(Config.class)
.withOperandFor(joinClass)
.withRight(right));
}
@Deprecated // to be removed before 2.0
public JoinPushThroughJoinRule(String description, boolean right,
Class extends Join> joinClass, ProjectFactory projectFactory) {
this(Config.LEFT.withDescription(description)
.withRelBuilderFactory(RelBuilder.proto(projectFactory))
.as(Config.class)
.withOperandFor(joinClass)
.withRight(right));
}
@Override public void onMatch(RelOptRuleCall call) {
if (config.isRight()) {
onMatchRight(call);
} else {
onMatchLeft(call);
}
}
private static void onMatchRight(RelOptRuleCall call) {
final Join topJoin = call.rel(0);
final Join bottomJoin = call.rel(1);
final RelNode relC = call.rel(2);
final RelNode relA = bottomJoin.getLeft();
final RelNode relB = bottomJoin.getRight();
final RelOptCluster cluster = topJoin.getCluster();
// topJoin
// / \
// bottomJoin C
// / \
// A B
final int aCount = relA.getRowType().getFieldCount();
final int bCount = relB.getRowType().getFieldCount();
final int cCount = relC.getRowType().getFieldCount();
final ImmutableBitSet bBitSet =
ImmutableBitSet.range(aCount, aCount + bCount);
// becomes
//
// newTopJoin
// / \
// newBottomJoin B
// / \
// A C
// If either join is not inner, we cannot proceed.
// (Is this too strict?)
if (topJoin.getJoinType() != JoinRelType.INNER
|| bottomJoin.getJoinType() != JoinRelType.INNER) {
return;
}
// Split the condition of topJoin into a conjunction. Each of the
// parts that does not use columns from B can be pushed down.
final List intersecting = new ArrayList<>();
final List nonIntersecting = new ArrayList<>();
split(topJoin.getCondition(), bBitSet, intersecting, nonIntersecting);
// If there's nothing to push down, it's not worth proceeding.
if (nonIntersecting.isEmpty()) {
return;
}
// Split the condition of bottomJoin into a conjunction. Each of the
// parts that use columns from B will need to be pulled up.
final List bottomIntersecting = new ArrayList<>();
final List bottomNonIntersecting = new ArrayList<>();
split(
bottomJoin.getCondition(), bBitSet, bottomIntersecting,
bottomNonIntersecting);
// target: | A | C |
// source: | A | B | C |
final Mappings.TargetMapping bottomMapping =
Mappings.createShiftMapping(
aCount + bCount + cCount,
0, 0, aCount,
aCount, aCount + bCount, cCount);
final List newBottomList = new ArrayList<>();
new RexPermuteInputsShuttle(bottomMapping, relA, relC)
.visitList(nonIntersecting, newBottomList);
new RexPermuteInputsShuttle(bottomMapping, relA, relC)
.visitList(bottomNonIntersecting, newBottomList);
final RexBuilder rexBuilder = cluster.getRexBuilder();
RexNode newBottomCondition =
RexUtil.composeConjunction(rexBuilder, newBottomList);
final Join newBottomJoin =
bottomJoin.copy(bottomJoin.getTraitSet(), newBottomCondition, relA,
relC, bottomJoin.getJoinType(), bottomJoin.isSemiJoinDone());
// target: | A | C | B |
// source: | A | B | C |
final Mappings.TargetMapping topMapping =
Mappings.createShiftMapping(
aCount + bCount + cCount,
0, 0, aCount,
aCount + cCount, aCount, bCount,
aCount, aCount + bCount, cCount);
final List newTopList = new ArrayList<>();
new RexPermuteInputsShuttle(topMapping, newBottomJoin, relB)
.visitList(intersecting, newTopList);
new RexPermuteInputsShuttle(topMapping, newBottomJoin, relB)
.visitList(bottomIntersecting, newTopList);
RexNode newTopCondition =
RexUtil.composeConjunction(rexBuilder, newTopList);
@SuppressWarnings("SuspiciousNameCombination")
final Join newTopJoin =
topJoin.copy(topJoin.getTraitSet(), newTopCondition, newBottomJoin,
relB, topJoin.getJoinType(), topJoin.isSemiJoinDone());
assert !Mappings.isIdentity(topMapping);
final RelBuilder relBuilder = call.builder();
relBuilder.push(newTopJoin);
relBuilder.project(relBuilder.fields(topMapping));
call.transformTo(relBuilder.build());
}
/**
* Similar to {@link #onMatch}, but swaps the upper sibling with the left
* of the two lower siblings, rather than the right.
*/
private static void onMatchLeft(RelOptRuleCall call) {
final Join topJoin = call.rel(0);
final Join bottomJoin = call.rel(1);
final RelNode relC = call.rel(2);
final RelNode relA = bottomJoin.getLeft();
final RelNode relB = bottomJoin.getRight();
final RelOptCluster cluster = topJoin.getCluster();
// topJoin
// / \
// bottomJoin C
// / \
// A B
final int aCount = relA.getRowType().getFieldCount();
final int bCount = relB.getRowType().getFieldCount();
final int cCount = relC.getRowType().getFieldCount();
final ImmutableBitSet aBitSet = ImmutableBitSet.range(aCount);
// becomes
//
// newTopJoin
// / \
// newBottomJoin A
// / \
// C B
// If either join is not inner, we cannot proceed.
// (Is this too strict?)
if (topJoin.getJoinType() != JoinRelType.INNER
|| bottomJoin.getJoinType() != JoinRelType.INNER) {
return;
}
// Split the condition of topJoin into a conjunction. Each of the
// parts that does not use columns from A can be pushed down.
final List intersecting = new ArrayList<>();
final List nonIntersecting = new ArrayList<>();
split(topJoin.getCondition(), aBitSet, intersecting, nonIntersecting);
// If there's nothing to push down, it's not worth proceeding.
if (nonIntersecting.isEmpty()) {
return;
}
// Split the condition of bottomJoin into a conjunction. Each of the
// parts that use columns from A will need to be pulled up.
final List bottomIntersecting = new ArrayList<>();
final List bottomNonIntersecting = new ArrayList<>();
split(
bottomJoin.getCondition(), aBitSet, bottomIntersecting,
bottomNonIntersecting);
// target: | C | B |
// source: | A | B | C |
final Mappings.TargetMapping bottomMapping =
Mappings.createShiftMapping(
aCount + bCount + cCount,
cCount, aCount, bCount,
0, aCount + bCount, cCount);
final List newBottomList = new ArrayList<>();
new RexPermuteInputsShuttle(bottomMapping, relC, relB)
.visitList(nonIntersecting, newBottomList);
new RexPermuteInputsShuttle(bottomMapping, relC, relB)
.visitList(bottomNonIntersecting, newBottomList);
final RexBuilder rexBuilder = cluster.getRexBuilder();
RexNode newBottomCondition =
RexUtil.composeConjunction(rexBuilder, newBottomList);
final Join newBottomJoin =
bottomJoin.copy(bottomJoin.getTraitSet(), newBottomCondition, relC,
relB, bottomJoin.getJoinType(), bottomJoin.isSemiJoinDone());
// target: | C | B | A |
// source: | A | B | C |
final Mappings.TargetMapping topMapping =
Mappings.createShiftMapping(
aCount + bCount + cCount,
cCount + bCount, 0, aCount,
cCount, aCount, bCount,
0, aCount + bCount, cCount);
final List newTopList = new ArrayList<>();
new RexPermuteInputsShuttle(topMapping, newBottomJoin, relA)
.visitList(intersecting, newTopList);
new RexPermuteInputsShuttle(topMapping, newBottomJoin, relA)
.visitList(bottomIntersecting, newTopList);
RexNode newTopCondition =
RexUtil.composeConjunction(rexBuilder, newTopList);
@SuppressWarnings("SuspiciousNameCombination")
final Join newTopJoin =
topJoin.copy(topJoin.getTraitSet(), newTopCondition, newBottomJoin,
relA, topJoin.getJoinType(), topJoin.isSemiJoinDone());
final RelBuilder relBuilder = call.builder();
relBuilder.push(newTopJoin);
relBuilder.project(relBuilder.fields(topMapping));
call.transformTo(relBuilder.build());
}
/**
* Splits a condition into conjunctions that do or do not intersect with
* a given bit set.
*/
static void split(
RexNode condition,
ImmutableBitSet bitSet,
List intersecting,
List nonIntersecting) {
for (RexNode node : RelOptUtil.conjunctions(condition)) {
ImmutableBitSet inputBitSet = RelOptUtil.InputFinder.bits(node);
if (bitSet.intersects(inputBitSet)) {
intersecting.add(node);
} else {
nonIntersecting.add(node);
}
}
}
/** Rule configuration. */
@Value.Immutable
public interface Config extends RelRule.Config {
Config RIGHT = ImmutableJoinPushThroughJoinRule.Config.of()
.withDescription("JoinPushThroughJoinRule:right")
.withOperandFor(LogicalJoin.class)
.withRight(true);
Config LEFT = ImmutableJoinPushThroughJoinRule.Config.of()
.withDescription("JoinPushThroughJoinRule:left")
.withOperandFor(LogicalJoin.class)
.withRight(false);
@Override default JoinPushThroughJoinRule toRule() {
return new JoinPushThroughJoinRule(this);
}
/** Whether to push on the right. If false, push to the left. */
@Value.Default default boolean isRight() {
return false;
}
/** Sets {@link #isRight()}. */
Config withRight(boolean right);
/** Defines an operand tree for the given classes. */
default Config withOperandFor(Class extends Join> joinClass) {
return withOperandSupplier(b0 ->
b0.operand(joinClass).inputs(
b1 -> b1.operand(joinClass).anyInputs(),
b2 -> b2.operand(RelNode.class)
.predicate(n -> !n.isEnforcer()).anyInputs()))
.as(Config.class);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy