This is an automated email from the ASF dual-hosted git repository.
zabetak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 618e601b13 [CALCITE-6846] Support basic DPhyp join reorder algorithm
618e601b13 is described below
commit 618e601b136e92db933523f77dd7af3c1dfe2779
Author: Silun <[email protected]>
AuthorDate: Fri Mar 7 08:39:44 2025 +0800
[CALCITE-6846] Support basic DPhyp join reorder algorithm
Close apache/calcite#4204
---
.../org/apache/calcite/rel/rules/CoreRules.java | 14 +
.../java/org/apache/calcite/rel/rules/DpHyp.java | 226 ++++++++++
.../calcite/rel/rules/DphypJoinReorderRule.java | 85 ++++
.../org/apache/calcite/rel/rules/HyperEdge.java | 82 ++++
.../org/apache/calcite/rel/rules/HyperGraph.java | 492 +++++++++++++++++++++
.../calcite/rel/rules/JoinToHyperGraphRule.java | 187 ++++++++
.../org/apache/calcite/rel/rules/LongBitmap.java | 141 ++++++
.../org/apache/calcite/test/RelOptRulesTest.java | 55 +++
.../org/apache/calcite/test/RelOptRulesTest.xml | 84 ++++
9 files changed, 1366 insertions(+)
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
index b88e1459b2..57611ffee6 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
@@ -16,6 +16,7 @@
*/
package org.apache.calcite.rel.rules;
+import org.apache.calcite.linq4j.function.Experimental;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.Calc;
@@ -816,4 +817,17 @@ private CoreRules() {}
WINDOW_REDUCE_EXPRESSIONS =
ReduceExpressionsRule.WindowReduceExpressionsRule.WindowReduceExpressionsRuleConfig
.DEFAULT.toRule();
+
+ /** Rule that flattens a tree of {@link LogicalJoin}s
+ * into a single {@link HyperGraph} with N inputs. */
+ @Experimental
+ public static final JoinToHyperGraphRule JOIN_TO_HYPER_GRAPH =
+ JoinToHyperGraphRule.Config.DEFAULT.toRule();
+
+ /** Rule that re-orders a {@link Join} tree using dphyp algorithm.
+ *
+ * @see #JOIN_TO_HYPER_GRAPH */
+ @Experimental
+ public static final DphypJoinReorderRule HYPER_GRAPH_OPTIMIZE =
+ DphypJoinReorderRule.Config.DEFAULT.toRule();
}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/DpHyp.java
b/core/src/main/java/org/apache/calcite/rel/rules/DpHyp.java
new file mode 100644
index 0000000000..63c82b9efc
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/DpHyp.java
@@ -0,0 +1,226 @@
+/*
+ * 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.linq4j.function.Experimental;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.tools.RelBuilder;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * The core process of dphyp enumeration algorithm.
+ */
+@Experimental
+public class DpHyp {
+
+ private final HyperGraph hyperGraph;
+
+ private final HashMap<Long, RelNode> dpTable;
+
+ private final RelBuilder builder;
+
+ private final RelMetadataQuery mq;
+
+ public DpHyp(HyperGraph hyperGraph, RelBuilder builder, RelMetadataQuery
relMetadataQuery) {
+ this.hyperGraph =
+ hyperGraph.copy(
+ hyperGraph.getTraitSet(),
+ hyperGraph.getInputs());
+ this.dpTable = new HashMap<>();
+ this.builder = builder;
+ this.mq = relMetadataQuery;
+ // make all field name unique and convert the
+ // HyperEdge condition from RexInputRef to RexInputFieldName
+ this.hyperGraph.convertHyperEdgeCond(builder);
+ }
+
+ /**
+ * The entry function of the algorithm. We use a bitmap to represent a leaf
node,
+ * which indicates the position of the corresponding leaf node in {@link
HyperGraph}.
+ *
+ * <p>After the enumeration is completed, the best join order will be stored
+ * in the {@link DpHyp#dpTable}.
+ */
+ public void startEnumerateJoin() {
+ int size = hyperGraph.getInputs().size();
+ for (int i = 0; i < size; i++) {
+ long singleNode = LongBitmap.newBitmap(i);
+ dpTable.put(singleNode, hyperGraph.getInput(i));
+ hyperGraph.initEdgeBitMap(singleNode);
+ }
+
+ // start enumerating from the second to last
+ for (int i = size - 2; i >= 0; i--) {
+ long csg = LongBitmap.newBitmap(i);
+ long forbidden = csg - 1;
+ emitCsg(csg);
+ enumerateCsgRec(csg, forbidden);
+ }
+ }
+
+ /**
+ * Given a connected subgraph (csg), enumerate all possible complements
subgraph (cmp)
+ * that do not include anything from the exclusion subset.
+ *
+ * <p>Corresponding to EmitCsg in origin paper.
+ */
+ private void emitCsg(long csg) {
+ long forbidden = csg | LongBitmap.getBvBitmap(csg);
+ long neighbors = hyperGraph.getNeighborBitmap(csg, forbidden);
+
+ LongBitmap.ReverseIterator reverseIterator = new
LongBitmap.ReverseIterator(neighbors);
+ for (long cmp : reverseIterator) {
+ List<HyperEdge> edges = hyperGraph.connectCsgCmp(csg, cmp);
+ if (!edges.isEmpty()) {
+ emitCsgCmp(csg, cmp, edges);
+ }
+ // forbidden the nodes that smaller than current cmp when extend cmp,
e.g.
+ // neighbors = {t1, t2}, t1 and t2 are connected.
+ // when extented t2, we will get (t1, t2)
+ // when extented t1, we will get (t1, t2) repeated
+ long newForbidden =
+ (cmp | LongBitmap.getBvBitmap(cmp)) & neighbors;
+ newForbidden = newForbidden | forbidden;
+ enumerateCmpRec(csg, cmp, newForbidden);
+ }
+ }
+
+ /**
+ * Given a connected subgraph (csg), expands it recursively by its neighbors.
+ * If the expanded csg is connected, try to enumerate its cmp (note that for
complex hyperedge,
+ * we only select a single representative node to add to the neighbors, so
csg and subNeighbor
+ * are not necessarily connected. However, it still needs to be expanded to
prevent missing
+ * complex hyperedge). This method is called after the enumeration of csg is
completed,
+ * that is, after {@link DpHyp#emitCsg(long csg)}.
+ *
+ * <p>Corresponding to EnumerateCsgRec in origin paper.
+ */
+ private void enumerateCsgRec(long csg, long forbidden) {
+ long neighbors = hyperGraph.getNeighborBitmap(csg, forbidden);
+ LongBitmap.SubsetIterator subsetIterator = new
LongBitmap.SubsetIterator(neighbors);
+ for (long subNeighbor : subsetIterator) {
+ hyperGraph.updateEdgesForUnion(csg, subNeighbor);
+ long newCsg = csg | subNeighbor;
+ if (dpTable.containsKey(newCsg)) {
+ emitCsg(newCsg);
+ }
+ }
+ long newForbidden = forbidden | neighbors;
+ subsetIterator.reset();
+ for (long subNeighbor : subsetIterator) {
+ long newCsg = csg | subNeighbor;
+ enumerateCsgRec(newCsg, newForbidden);
+ }
+ }
+
+ /**
+ * Given a connected subgraph (csg) and its complement subgraph (cmp),
expands the cmp
+ * recursively by neighbors of cmp (cmp and subNeighbor are not necessarily
connected,
+ * which is the same logic as in {@link DpHyp#enumerateCsgRec}).
+ *
+ * <p>Corresponding to EnumerateCmpRec in origin paper.
+ */
+ private void enumerateCmpRec(long csg, long cmp, long forbidden) {
+ long neighbors = hyperGraph.getNeighborBitmap(cmp, forbidden);
+ LongBitmap.SubsetIterator subsetIterator = new
LongBitmap.SubsetIterator(neighbors);
+ for (long subNeighbor : subsetIterator) {
+ long newCmp = cmp | subNeighbor;
+ hyperGraph.updateEdgesForUnion(cmp, subNeighbor);
+ if (dpTable.containsKey(newCmp)) {
+ List<HyperEdge> edges = hyperGraph.connectCsgCmp(csg, newCmp);
+ if (!edges.isEmpty()) {
+ emitCsgCmp(csg, newCmp, edges);
+ }
+ }
+ }
+ long newForbidden = forbidden | neighbors;
+ subsetIterator.reset();
+ for (long subNeighbor : subsetIterator) {
+ long newCmp = cmp | subNeighbor;
+ enumerateCmpRec(csg, newCmp, newForbidden);
+ }
+ }
+
+ /**
+ * Given a connected csg-cmp pair and the hyperedges that connect them,
build the
+ * corresponding Join plan. If the new Join plan is better than the existing
plan,
+ * update the {@link DpHyp#dpTable}.
+ *
+ * <p>Corresponding to EmitCsgCmp in origin paper.
+ */
+ private void emitCsgCmp(long csg, long cmp, List<HyperEdge> edges) {
+ RelNode child1 = dpTable.get(csg);
+ RelNode child2 = dpTable.get(cmp);
+ if (child1 == null || child2 == null) {
+ throw new IllegalArgumentException(
+ "csg and cmp were not enumerated in the previous dp process");
+ }
+
+ JoinRelType joinType = hyperGraph.extractJoinType(edges);
+ if (joinType == null) {
+ return;
+ }
+ RexNode joinCond1 = hyperGraph.extractJoinCond(child1, child2, edges);
+ RelNode newPlan1 = builder
+ .push(child1)
+ .push(child2)
+ .join(joinType, joinCond1)
+ .build();
+
+ // swap left and right
+ RexNode joinCond2 = hyperGraph.extractJoinCond(child2, child1, edges);
+ RelNode newPlan2 = builder
+ .push(child2)
+ .push(child1)
+ .join(joinType, joinCond2)
+ .build();
+ RelNode winPlan = chooseBetterPlan(newPlan1, newPlan2);
+
+ RelNode oriPlan = dpTable.get(csg | cmp);
+ if (oriPlan != null) {
+ winPlan = chooseBetterPlan(winPlan, oriPlan);
+ }
+ dpTable.put(csg | cmp, winPlan);
+ }
+
+ public @Nullable RelNode getBestPlan() {
+ int size = hyperGraph.getInputs().size();
+ long wholeGraph = LongBitmap.newBitmapBetween(0, size);
+ return dpTable.get(wholeGraph);
+ }
+
+ private RelNode chooseBetterPlan(RelNode plan1, RelNode plan2) {
+ RelOptCost cost1 = mq.getCumulativeCost(plan1);
+ RelOptCost cost2 = mq.getCumulativeCost(plan2);
+ if (cost1 != null && cost2 != null) {
+ return cost1.isLt(cost2) ? plan1 : plan2;
+ } else if (cost1 != null) {
+ return plan1;
+ } else {
+ return plan2;
+ }
+ }
+
+}
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/DphypJoinReorderRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/DphypJoinReorderRule.java
new file mode 100644
index 0000000000..8b4a517722
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/DphypJoinReorderRule.java
@@ -0,0 +1,85 @@
+/*
+ * 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.linq4j.function.Experimental;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.tools.RelBuilder;
+
+import org.immutables.value.Value;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Rule that re-orders a {@link Join} tree using dphyp algorithm.
+ *
+ * @see CoreRules#HYPER_GRAPH_OPTIMIZE */
[email protected]
+@Experimental
+public class DphypJoinReorderRule
+ extends RelRule<DphypJoinReorderRule.Config>
+ implements TransformationRule {
+
+ protected DphypJoinReorderRule(Config config) {
+ super(config);
+ }
+
+ @Override public void onMatch(RelOptRuleCall call) {
+ HyperGraph hyperGraph = call.rel(0);
+ RelBuilder relBuilder = call.builder();
+
+ // enumerate by Dphyp
+ DpHyp dpHyp = new DpHyp(hyperGraph, relBuilder, call.getMetadataQuery());
+ dpHyp.startEnumerateJoin();
+ RelNode orderedJoin = dpHyp.getBestPlan();
+ if (orderedJoin == null) {
+ return;
+ }
+
+ // permute field to origin order
+ List<String> oriNames = hyperGraph.getRowType().getFieldNames();
+ List<String> newNames = orderedJoin.getRowType().getFieldNames();
+ List<RexNode> projects = new ArrayList<>();
+ RexBuilder rexBuilder = hyperGraph.getCluster().getRexBuilder();
+ for (String oriName : oriNames) {
+ projects.add(rexBuilder.makeInputRef(orderedJoin,
newNames.indexOf(oriName)));
+ }
+
+ RelNode result = call.builder()
+ .push(orderedJoin)
+ .project(projects)
+ .build();
+ call.transformTo(result);
+ }
+
+ /** Rule configuration. */
+ @Value.Immutable
+ public interface Config extends RelRule.Config {
+ Config DEFAULT = ImmutableDphypJoinReorderRule.Config.of()
+ .withOperandSupplier(b1 ->
+ b1.operand(HyperGraph.class).anyInputs());
+
+ @Override default DphypJoinReorderRule toRule() {
+ return new DphypJoinReorderRule(this);
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/HyperEdge.java
b/core/src/main/java/org/apache/calcite/rel/rules/HyperEdge.java
new file mode 100644
index 0000000000..f9f85a43e6
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/HyperEdge.java
@@ -0,0 +1,82 @@
+/*
+ * 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.linq4j.function.Experimental;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rex.RexNode;
+
+/**
+ * Edge in HyperGraph, that represents a join predicate.
+ */
+@Experimental
+public class HyperEdge {
+
+ private final long leftNodeBits;
+
+ private final long rightNodeBits;
+
+ private final JoinRelType joinType;
+
+ private final boolean isSimple;
+
+ private final RexNode condition;
+
+ public HyperEdge(long leftNodeBits, long rightNodeBits, JoinRelType
joinType, RexNode condition) {
+ this.leftNodeBits = leftNodeBits;
+ this.rightNodeBits = rightNodeBits;
+ this.joinType = joinType;
+ this.condition = condition;
+ boolean leftSimple = (leftNodeBits & (leftNodeBits - 1)) == 0;
+ boolean rightSimple = (rightNodeBits & (rightNodeBits - 1)) == 0;
+ this.isSimple = leftSimple && rightSimple;
+ }
+
+ public long getNodeBitmap() {
+ return leftNodeBits | rightNodeBits;
+ }
+
+ public long getLeftNodeBitmap() {
+ return leftNodeBits;
+ }
+
+ public long getRightNodeBitmap() {
+ return rightNodeBits;
+ }
+
+ // hyperedge (u, v) is simple if |u| = |v| = 1
+ public boolean isSimple() {
+ return isSimple;
+ }
+
+ public JoinRelType getJoinType() {
+ return joinType;
+ }
+
+ public RexNode getCondition() {
+ return condition;
+ }
+
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(LongBitmap.printBitmap(leftNodeBits))
+ .append("——[").append(joinType).append(",
").append(condition).append("]——")
+ .append(LongBitmap.printBitmap(rightNodeBits));
+ return sb.toString();
+ }
+
+}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java
b/core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java
new file mode 100644
index 0000000000..d8fe0fd2ff
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java
@@ -0,0 +1,492 @@
+/*
+ * 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.linq4j.Ord;
+import org.apache.calcite.linq4j.function.Experimental;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBiVisitor;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.rex.RexVariable;
+import org.apache.calcite.rex.RexVisitor;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * HyperGraph represents a join graph.
+ */
+@Experimental
+public class HyperGraph extends AbstractRelNode {
+
+ private final List<RelNode> inputs;
+
+ @SuppressWarnings("HidingField")
+ private final RelDataType rowType;
+
+ private final List<HyperEdge> edges;
+
+ // record the indices of complex hyper edges in the 'edges'
+ private final ImmutableBitSet complexEdgesBitmap;
+
+ /**
+ * For the HashMap fields, key is the bitmap for inputs,
+ * value is the hyper edge bitmap in edges.
+ */
+ // record which hyper edges have been used by the enumerated csg-cmp pairs
+ private final HashMap<Long, BitSet> ccpUsedEdgesMap;
+
+ private final HashMap<Long, BitSet> simpleEdgesMap;
+
+ private final HashMap<Long, BitSet> complexEdgesMap;
+
+ // node bitmap overlaps edge's leftNodeBits or rightNodeBits, but does not
completely cover
+ private final HashMap<Long, BitSet> overlapEdgesMap;
+
+ protected HyperGraph(RelOptCluster cluster,
+ RelTraitSet traitSet,
+ List<RelNode> inputs,
+ List<HyperEdge> edges,
+ RelDataType rowType) {
+ super(cluster, traitSet);
+ this.inputs = Lists.newArrayList(inputs);
+ this.edges = Lists.newArrayList(edges);
+ this.rowType = rowType;
+ ImmutableBitSet.Builder bitSetBuilder = ImmutableBitSet.builder();
+ for (int i = 0; i < edges.size(); i++) {
+ if (!edges.get(i).isSimple()) {
+ bitSetBuilder.set(i);
+ }
+ }
+ this.complexEdgesBitmap = bitSetBuilder.build();
+ this.ccpUsedEdgesMap = new HashMap<>();
+ this.simpleEdgesMap = new HashMap<>();
+ this.complexEdgesMap = new HashMap<>();
+ this.overlapEdgesMap = new HashMap<>();
+ }
+
+ protected HyperGraph(RelOptCluster cluster,
+ RelTraitSet traitSet,
+ List<RelNode> inputs,
+ List<HyperEdge> edges,
+ RelDataType rowType,
+ ImmutableBitSet complexEdgesBitmap,
+ HashMap<Long, BitSet> ccpUsedEdgesMap,
+ HashMap<Long, BitSet> simpleEdgesMap,
+ HashMap<Long, BitSet> complexEdgesMap,
+ HashMap<Long, BitSet> overlapEdgesMap) {
+ super(cluster, traitSet);
+ this.inputs = Lists.newArrayList(inputs);
+ this.edges = Lists.newArrayList(edges);
+ this.rowType = rowType;
+ this.complexEdgesBitmap = complexEdgesBitmap;
+ this.ccpUsedEdgesMap = new HashMap<>(ccpUsedEdgesMap);
+ this.simpleEdgesMap = new HashMap<>(simpleEdgesMap);
+ this.complexEdgesMap = new HashMap<>(complexEdgesMap);
+ this.overlapEdgesMap = new HashMap<>(overlapEdgesMap);
+ }
+
+ @Override public HyperGraph copy(RelTraitSet traitSet, List<RelNode> inputs)
{
+ return new HyperGraph(
+ getCluster(),
+ traitSet,
+ inputs,
+ edges,
+ rowType,
+ complexEdgesBitmap,
+ ccpUsedEdgesMap,
+ simpleEdgesMap,
+ complexEdgesMap,
+ overlapEdgesMap);
+ }
+
+ @Override public RelWriter explainTerms(RelWriter pw) {
+ super.explainTerms(pw);
+ for (Ord<RelNode> ord : Ord.zip(inputs)) {
+ pw.input("input#" + ord.i, ord.e);
+ }
+ List<String> hyperEdges = edges.stream()
+ .map(hyperEdge -> hyperEdge.toString())
+ .collect(Collectors.toList());
+ pw.item("edges", String.join(",", hyperEdges));
+ return pw;
+ }
+
+ @Override public List<RelNode> getInputs() {
+ return inputs;
+ }
+
+ @Override public void replaceInput(int ordinalInParent, RelNode p) {
+ inputs.set(ordinalInParent, p);
+ recomputeDigest();
+ }
+
+ @Override public RelDataType deriveRowType() {
+ return rowType;
+ }
+
+ @Override public RelNode accept(RexShuttle shuttle) {
+ List<HyperEdge> shuttleEdges = new ArrayList<>();
+ for (HyperEdge edge : edges) {
+ HyperEdge shuttleEdge =
+ new HyperEdge(
+ edge.getLeftNodeBitmap(),
+ edge.getRightNodeBitmap(),
+ edge.getJoinType(),
+ shuttle.apply(edge.getCondition()));
+ shuttleEdges.add(shuttleEdge);
+ }
+
+ return new HyperGraph(
+ getCluster(),
+ traitSet,
+ inputs,
+ shuttleEdges,
+ rowType,
+ complexEdgesBitmap,
+ ccpUsedEdgesMap,
+ simpleEdgesMap,
+ complexEdgesMap,
+ overlapEdgesMap);
+ }
+
+ //~ hyper graph method
----------------------------------------------------------
+
+ public List<HyperEdge> getEdges() {
+ return edges;
+ }
+
+ public long getNeighborBitmap(long csg, long forbidden) {
+ long neighbors = 0L;
+ List<HyperEdge> simpleEdges = simpleEdgesMap.getOrDefault(csg, new
BitSet()).stream()
+ .mapToObj(edges::get)
+ .collect(Collectors.toList());
+ for (HyperEdge edge : simpleEdges) {
+ neighbors |= edge.getNodeBitmap();
+ }
+
+ forbidden = forbidden | csg;
+ neighbors = neighbors & ~forbidden;
+ forbidden = forbidden | neighbors;
+
+ List<HyperEdge> complexEdges = complexEdgesMap.getOrDefault(csg, new
BitSet()).stream()
+ .mapToObj(edges::get)
+ .collect(Collectors.toList());
+ for (HyperEdge edge : complexEdges) {
+ long leftBitmap = edge.getLeftNodeBitmap();
+ long rightBitmap = edge.getRightNodeBitmap();
+ if (LongBitmap.isSubSet(leftBitmap, csg) &&
!LongBitmap.isOverlap(rightBitmap, forbidden)) {
+ neighbors |= Long.lowestOneBit(rightBitmap);
+ } else if (LongBitmap.isSubSet(rightBitmap, csg)
+ && !LongBitmap.isOverlap(leftBitmap, forbidden)) {
+ neighbors |= Long.lowestOneBit(leftBitmap);
+ }
+ }
+ return neighbors;
+ }
+
+ /**
+ * If csg and cmp are connected, return the edges that connect them.
+ */
+ public List<HyperEdge> connectCsgCmp(long csg, long cmp) {
+ checkArgument(simpleEdgesMap.containsKey(csg));
+ checkArgument(simpleEdgesMap.containsKey(cmp));
+ List<HyperEdge> connectedEdges = new ArrayList<>();
+ BitSet connectedEdgesBitmap = new BitSet();
+ connectedEdgesBitmap.or(simpleEdgesMap.getOrDefault(csg, new BitSet()));
+ connectedEdgesBitmap.or(complexEdgesMap.getOrDefault(csg, new BitSet()));
+
+ BitSet cmpEdgesBitmap = new BitSet();
+ cmpEdgesBitmap.or(simpleEdgesMap.getOrDefault(cmp, new BitSet()));
+ cmpEdgesBitmap.or(complexEdgesMap.getOrDefault(cmp, new BitSet()));
+ connectedEdgesBitmap.and(cmpEdgesBitmap);
+
+ // only consider the records related to csg and cmp in the
simpleEdgesMap/complexEdgesMap,
+ // may omit some complex hyper edges. e.g.
+ // csg = {t1, t3}, cmp = {t2}, will omit the edge (t1, t2)——(t3)
+ BitSet mayMissedEdges = new BitSet();
+ mayMissedEdges.or(complexEdgesBitmap.toBitSet());
+ mayMissedEdges.andNot(ccpUsedEdgesMap.getOrDefault(csg, new BitSet()));
+ mayMissedEdges.andNot(ccpUsedEdgesMap.getOrDefault(cmp, new BitSet()));
+ mayMissedEdges.andNot(connectedEdgesBitmap);
+ mayMissedEdges.stream()
+ .forEach(index -> {
+ HyperEdge edge = edges.get(index);
+ if (LongBitmap.isSubSet(edge.getNodeBitmap(), csg | cmp)) {
+ connectedEdgesBitmap.set(index);
+ }
+ });
+
+ // record hyper edges are used by current csg ∪ cmp
+ BitSet curUsedEdges = new BitSet();
+ curUsedEdges.or(connectedEdgesBitmap);
+ curUsedEdges.or(ccpUsedEdgesMap.getOrDefault(csg, new BitSet()));
+ curUsedEdges.or(ccpUsedEdgesMap.getOrDefault(cmp, new BitSet()));
+ if (ccpUsedEdgesMap.containsKey(csg | cmp)) {
+ checkArgument(
+ curUsedEdges.equals(ccpUsedEdgesMap.get(csg | cmp)));
+ }
+ ccpUsedEdgesMap.put(csg | cmp, curUsedEdges);
+
+ connectedEdgesBitmap.stream()
+ .forEach(index -> connectedEdges.add(edges.get(index)));
+ return connectedEdges;
+ }
+
+ public void initEdgeBitMap(long subset) {
+ BitSet simpleBitSet = new BitSet();
+ BitSet complexBitSet = new BitSet();
+ BitSet overlapBitSet = new BitSet();
+ for (int i = 0; i < edges.size(); i++) {
+ HyperEdge edge = edges.get(i);
+ if (isAccurateEdge(edge, subset)) {
+ if (edge.isSimple()) {
+ simpleBitSet.set(i);
+ } else {
+ complexBitSet.set(i);
+ }
+ } else if (isOverlapEdge(edge, subset)) {
+ overlapBitSet.set(i);
+ }
+ }
+ simpleEdgesMap.put(subset, simpleBitSet);
+ complexEdgesMap.put(subset, complexBitSet);
+ overlapEdgesMap.put(subset, overlapBitSet);
+ }
+
+ public void updateEdgesForUnion(long subset1, long subset2) {
+ if (!simpleEdgesMap.containsKey(subset1)) {
+ initEdgeBitMap(subset1);
+ }
+ if (!simpleEdgesMap.containsKey(subset2)) {
+ initEdgeBitMap(subset2);
+ }
+ long unionSet = subset1 | subset2;
+ if (simpleEdgesMap.containsKey(unionSet)) {
+ return;
+ }
+
+ BitSet unionSimpleBitSet = new BitSet();
+ unionSimpleBitSet.or(simpleEdgesMap.getOrDefault(subset1, new BitSet()));
+ unionSimpleBitSet.or(simpleEdgesMap.getOrDefault(subset2, new BitSet()));
+
+ BitSet unionComplexBitSet = new BitSet();
+ unionComplexBitSet.or(complexEdgesMap.getOrDefault(subset1, new BitSet()));
+ unionComplexBitSet.or(complexEdgesMap.getOrDefault(subset2, new BitSet()));
+
+ BitSet unionOverlapBitSet = new BitSet();
+ unionOverlapBitSet.or(overlapEdgesMap.getOrDefault(subset1, new BitSet()));
+ unionOverlapBitSet.or(overlapEdgesMap.getOrDefault(subset2, new BitSet()));
+
+ // the overlaps edge that belongs to subset1/subset2
+ // may be complex edge for subset1 union subset2
+ for (int index : unionOverlapBitSet.stream().toArray()) {
+ HyperEdge edge = edges.get(index);
+ if (isAccurateEdge(edge, unionSet)) {
+ unionComplexBitSet.set(index);
+ unionOverlapBitSet.set(index, false);
+ }
+ }
+
+ // remove cycle in subset1 union subset2
+ for (int index : unionSimpleBitSet.stream().toArray()) {
+ HyperEdge edge = edges.get(index);
+ if (!isAccurateEdge(edge, unionSet)) {
+ unionSimpleBitSet.set(index, false);
+ }
+ }
+ for (int index : unionComplexBitSet.stream().toArray()) {
+ HyperEdge edge = edges.get(index);
+ if (!isAccurateEdge(edge, unionSet)) {
+ unionComplexBitSet.set(index, false);
+ }
+ }
+
+ simpleEdgesMap.put(unionSet, unionSimpleBitSet);
+ complexEdgesMap.put(unionSet, unionComplexBitSet);
+ overlapEdgesMap.put(unionSet, unionOverlapBitSet);
+ }
+
+ private static boolean isAccurateEdge(HyperEdge edge, long subset) {
+ boolean isLeftEnd = LongBitmap.isSubSet(edge.getLeftNodeBitmap(), subset)
+ && !LongBitmap.isOverlap(edge.getRightNodeBitmap(), subset);
+ boolean isRightEnd = LongBitmap.isSubSet(edge.getRightNodeBitmap(), subset)
+ && !LongBitmap.isOverlap(edge.getLeftNodeBitmap(), subset);
+ return isLeftEnd || isRightEnd;
+ }
+
+ private static boolean isOverlapEdge(HyperEdge edge, long subset) {
+ boolean isLeftEnd = LongBitmap.isOverlap(edge.getLeftNodeBitmap(), subset)
+ && !LongBitmap.isOverlap(edge.getRightNodeBitmap(), subset);
+ boolean isRightEnd = LongBitmap.isOverlap(edge.getRightNodeBitmap(),
subset)
+ && !LongBitmap.isOverlap(edge.getLeftNodeBitmap(), subset);
+ return isLeftEnd || isRightEnd;
+ }
+
+ public @Nullable JoinRelType extractJoinType(List<HyperEdge> edges) {
+ JoinRelType joinType = edges.get(0).getJoinType();
+ for (int i = 1; i < edges.size(); i++) {
+ if (edges.get(i).getJoinType() != joinType) {
+ return null;
+ }
+ }
+ return joinType;
+ }
+
+ public RexNode extractJoinCond(RelNode left, RelNode right, List<HyperEdge>
edges) {
+ List<RexNode> joinConds = new ArrayList<>();
+ List<RelDataTypeField> fieldList = new
ArrayList<>(left.getRowType().getFieldList());
+ fieldList.addAll(right.getRowType().getFieldList());
+
+ List<String> names = new ArrayList<>(left.getRowType().getFieldNames());
+ names.addAll(right.getRowType().getFieldNames());
+
+ // convert the HyperEdge's condition from RexInputFieldName to RexInputRef
+ RexShuttle inputName2InputRefShuttle = new RexShuttle() {
+ @Override protected List<RexNode> visitList(
+ List<? extends RexNode> exprs,
+ boolean @Nullable [] update) {
+ ImmutableList.Builder<RexNode> clonedOperands =
ImmutableList.builder();
+ for (RexNode operand : exprs) {
+ RexNode clonedOperand;
+ if (operand instanceof RexInputFieldName) {
+ int index = names.indexOf(((RexInputFieldName) operand).getName());
+ clonedOperand = new RexInputRef(index,
fieldList.get(index).getType());
+ } else {
+ clonedOperand = operand.accept(this);
+ }
+ if ((clonedOperand != operand) && (update != null)) {
+ update[0] = true;
+ }
+ clonedOperands.add(clonedOperand);
+ }
+ return clonedOperands.build();
+ }
+ };
+
+ for (HyperEdge edge : edges) {
+ RexNode inputRefCond =
edge.getCondition().accept(inputName2InputRefShuttle);
+ joinConds.add(inputRefCond);
+ }
+ return RexUtil.composeConjunction(left.getCluster().getRexBuilder(),
joinConds);
+ }
+
+ /**
+ * Before starting enumeration, add Project on every input, make all field
name unique.
+ * Convert the HyperEdge condition from RexInputRef to RexInputFieldName
+ */
+ public void convertHyperEdgeCond(RelBuilder builder) {
+ int fieldIndex = 0;
+ List<RelDataTypeField> fieldList = rowType.getFieldList();
+ for (int nodeIndex = 0; nodeIndex < inputs.size(); nodeIndex++) {
+ RelNode input = inputs.get(nodeIndex);
+ List<RexNode> projects = new ArrayList<>();
+ List<String> names = new ArrayList<>();
+ for (int i = 0; i < input.getRowType().getFieldCount(); i++) {
+ projects.add(
+ new RexInputRef(
+ i,
+ fieldList.get(fieldIndex).getType()));
+ names.add(fieldList.get(fieldIndex).getName());
+ fieldIndex++;
+ }
+
+ builder.push(input)
+ .project(projects, names, true);
+ replaceInput(nodeIndex, builder.build());
+ }
+
+ RexShuttle inputRef2inputNameShuttle = new RexShuttle() {
+ @Override public RexNode visitInputRef(RexInputRef inputRef) {
+ int index = inputRef.getIndex();
+ return new RexInputFieldName(
+ fieldList.get(index).getName(),
+ fieldList.get(index).getType());
+ }
+ };
+
+ for (int i = 0; i < edges.size(); i++) {
+ HyperEdge edge = edges.get(i);
+ RexNode convertCond =
edge.getCondition().accept(inputRef2inputNameShuttle);
+ HyperEdge convertEdge =
+ new HyperEdge(
+ edge.getLeftNodeBitmap(),
+ edge.getRightNodeBitmap(),
+ edge.getJoinType(),
+ convertCond);
+ edges.set(i, convertEdge);
+ }
+ }
+
+ /**
+ * Adjusting RexInputRef in enumeration process is too complicated,
+ * so use unique name replace input ref.
+ * Before starting enumeration, convert RexInputRef to RexInputFieldName.
+ * When connect csgcmp to Join, convert RexInputFieldName to RexInputRef.
+ */
+ private static class RexInputFieldName extends RexVariable {
+
+ RexInputFieldName(final String fieldName, final RelDataType type) {
+ super(fieldName, type);
+ }
+
+ @Override public <R> R accept(RexVisitor<R> visitor) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public boolean equals(@Nullable Object obj) {
+ return this == obj
+ || obj instanceof RexInputFieldName
+ && name == ((RexInputFieldName) obj).name
+ && type.equals(((RexInputFieldName) obj).type);
+ }
+
+ @Override public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override public String toString() {
+ return name;
+ }
+ }
+}
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/JoinToHyperGraphRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/JoinToHyperGraphRule.java
new file mode 100644
index 0000000000..3ed41e4d6e
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinToHyperGraphRule.java
@@ -0,0 +1,187 @@
+/*
+ * 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.linq4j.function.Experimental;
+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.JoinRelType;
+import org.apache.calcite.rel.logical.LogicalJoin;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.rex.RexVisitorImpl;
+
+import org.immutables.value.Value;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Rule that flattens a tree of {@link LogicalJoin}s
+ * into a single {@link HyperGraph} with N inputs.
+ *
+ * @see CoreRules#JOIN_TO_HYPER_GRAPH
+ */
[email protected]
+@Experimental
+public class JoinToHyperGraphRule
+ extends RelRule<JoinToHyperGraphRule.Config>
+ implements TransformationRule {
+
+ protected JoinToHyperGraphRule(Config config) {
+ super(config);
+ }
+
+ @Override public void onMatch(RelOptRuleCall call) {
+ final Join origJoin = call.rel(0);
+ final RelNode left = call.rel(1);
+ final RelNode right = call.rel(2);
+ if (origJoin.getJoinType() != JoinRelType.INNER) {
+ return;
+ }
+
+ HyperGraph result;
+ List<RelNode> inputs = new ArrayList<>();
+ List<HyperEdge> edges = new ArrayList<>();
+ List<RexNode> joinConds = new ArrayList<>();
+
+ if (origJoin.getCondition().isAlwaysTrue()) {
+ joinConds.add(origJoin.getCondition());
+ } else {
+ RelOptUtil.decomposeConjunction(origJoin.getCondition(), joinConds);
+ }
+
+ // when right is HyperGraph, need shift the leftNodeBit, rightNodeBit,
condition of HyperEdge
+ int leftNodeCount;
+ int leftFieldCount = left.getRowType().getFieldCount();
+ if (left instanceof HyperGraph && right instanceof HyperGraph) {
+ leftNodeCount = left.getInputs().size();
+ inputs.addAll(left.getInputs());
+ inputs.addAll(right.getInputs());
+
+ edges.addAll(((HyperGraph) left).getEdges());
+ edges.addAll(
+ ((HyperGraph) right).getEdges().stream()
+ .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount,
leftFieldCount))
+ .collect(Collectors.toList()));
+ } else if (left instanceof HyperGraph) {
+ leftNodeCount = left.getInputs().size();
+ inputs.addAll(left.getInputs());
+ inputs.add(right);
+
+ edges.addAll(((HyperGraph) left).getEdges());
+ } else if (right instanceof HyperGraph) {
+ leftNodeCount = 1;
+ inputs.add(left);
+ inputs.addAll(right.getInputs());
+
+ edges.addAll(
+ ((HyperGraph) right).getEdges().stream()
+ .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount,
leftFieldCount))
+ .collect(Collectors.toList()));
+ } else {
+ leftNodeCount = 1;
+ inputs.add(left);
+ inputs.add(right);
+ }
+
+ HashMap<Integer, Integer> fieldIndexToNodeIndexMap = new HashMap<>();
+ int fieldCount = 0;
+ for (int i = 0; i < inputs.size(); i++) {
+ for (int j = 0; j < inputs.get(i).getRowType().getFieldCount(); j++) {
+ fieldIndexToNodeIndexMap.put(fieldCount++, i);
+ }
+ }
+ // convert current join condition to hyper edge condition
+ for (RexNode joinCond : joinConds) {
+ long leftNodeBits;
+ long rightNodeBits;
+ List<Integer> leftRefs = new ArrayList<>();
+ List<Integer> rightRefs = new ArrayList<>();
+
+ RexVisitorImpl visitor = new RexVisitorImpl<Void>(true) {
+ @Override public Void visitInputRef(RexInputRef inputRef) {
+ Integer nodeIndex =
fieldIndexToNodeIndexMap.get(inputRef.getIndex());
+ if (nodeIndex == null) {
+ throw new IllegalArgumentException("RexInputRef refers a dummy
field: "
+ + inputRef + ", rowType is: " + origJoin.getRowType());
+ }
+ if (nodeIndex < leftNodeCount) {
+ leftRefs.add(nodeIndex);
+ } else {
+ rightRefs.add(nodeIndex);
+ }
+ return null;
+ }
+ };
+ joinCond.accept(visitor);
+
+ // when cartesian product, make it to complex hyper edge
+ if (leftRefs.isEmpty() || rightRefs.isEmpty()) {
+ leftNodeBits = LongBitmap.newBitmapBetween(0, leftNodeCount);
+ rightNodeBits = LongBitmap.newBitmapBetween(leftNodeCount,
inputs.size());
+ } else {
+ leftNodeBits = LongBitmap.newBitmapFromList(leftRefs);
+ rightNodeBits = LongBitmap.newBitmapFromList(rightRefs);
+ }
+ edges.add(
+ new HyperEdge(
+ leftNodeBits,
+ rightNodeBits,
+ origJoin.getJoinType(),
+ joinCond));
+ }
+ result =
+ new HyperGraph(
+ origJoin.getCluster(),
+ origJoin.getTraitSet(),
+ inputs,
+ edges,
+ origJoin.getRowType());
+
+ call.transformTo(result);
+ }
+
+ private static HyperEdge adjustNodeBit(HyperEdge hyperEdge, int nodeOffset,
int fieldOffset) {
+ RexNode newCondition = RexUtil.shift(hyperEdge.getCondition(),
fieldOffset);
+ return new HyperEdge(
+ hyperEdge.getLeftNodeBitmap() << nodeOffset,
+ hyperEdge.getRightNodeBitmap() << nodeOffset,
+ hyperEdge.getJoinType(),
+ newCondition);
+ }
+
+ /** Rule configuration. */
+ @Value.Immutable
+ public interface Config extends RelRule.Config {
+ Config DEFAULT = ImmutableJoinToHyperGraphRule.Config.of()
+ .withOperandSupplier(b1 ->
+ b1.operand(Join.class).inputs(
+ b2 -> b2.operand(RelNode.class).anyInputs(),
+ b3 -> b3.operand(RelNode.class).anyInputs()));
+
+ @Override default JoinToHyperGraphRule toRule() {
+ return new JoinToHyperGraphRule(this);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/LongBitmap.java
b/core/src/main/java/org/apache/calcite/rel/rules/LongBitmap.java
new file mode 100644
index 0000000000..39ca3ec586
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LongBitmap.java
@@ -0,0 +1,141 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Bitmap tool for dphyp.
+ */
+public class LongBitmap {
+
+ private LongBitmap() {}
+
+ public static long newBitmapBetween(int startInclude, int endExclude) {
+ long bitmap = 0;
+ for (int i = startInclude; i < endExclude; i++) {
+ bitmap |= 1L << i;
+ }
+ return bitmap;
+ }
+
+ public static long newBitmap(int value) {
+ return 1L << value;
+ }
+
+ /**
+ * Corresponding to Bv = {node|node ≺ csg} in "Dynamic programming strikes
back".
+ */
+ public static long getBvBitmap(long csg) {
+ return (csg & -csg) - 1;
+ }
+
+ public static boolean isSubSet(long maySub, long bigger) {
+ return (bigger | maySub) == bigger;
+ }
+
+ public static boolean isOverlap(long bitmap1, long bitmap2) {
+ return (bitmap1 & bitmap2) != 0;
+ }
+
+ public static long newBitmapFromList(List<Integer> values) {
+ long bitmap = 0;
+ for (int value : values) {
+ bitmap |= 1L << value;
+ }
+ return bitmap;
+ }
+
+ public static String printBitmap(long bitmap) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ while (bitmap != 0) {
+ sb.append(Long.numberOfTrailingZeros(bitmap)).append(", ");
+ bitmap = bitmap & (bitmap - 1);
+ }
+ sb.delete(sb.length() - 2, sb.length());
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Traverse the bitmap in reverse order.
+ */
+ public static class ReverseIterator implements Iterable<Long> {
+
+ private long bitmap;
+
+ public ReverseIterator(long bitmap) {
+ this.bitmap = bitmap;
+ }
+
+ @Override public Iterator<Long> iterator() {
+ return new Iterator<Long>() {
+ @Override public boolean hasNext() {
+ return bitmap != 0;
+ }
+
+ @Override public Long next() {
+ long res = Long.highestOneBit(bitmap);
+ bitmap &= ~res;
+ return res;
+ }
+ };
+ }
+ }
+
+ /**
+ * Enumerate all subsets of a bitmap from small to large.
+ */
+ public static class SubsetIterator implements Iterable<Long> {
+
+ private ArrayList<Long> subsetList;
+
+ private int index;
+
+ public SubsetIterator(long bitmap) {
+ long curBiggestSubset = bitmap;
+ this.subsetList = new ArrayList<>();
+
+ while (curBiggestSubset != 0) {
+ subsetList.add(curBiggestSubset);
+ curBiggestSubset = (curBiggestSubset - 1) & bitmap;
+ }
+
+ this.index = subsetList.size() - 1;
+ }
+
+ @Override public Iterator<Long> iterator() {
+ return new Iterator<Long>() {
+ @Override public boolean hasNext() {
+ return index >= 0;
+ }
+
+ @Override public Long next() {
+ return subsetList.get(index--);
+ }
+ };
+ }
+
+ public void reset() {
+ index = subsetList.size() - 1;
+ }
+ }
+
+}
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index cc2b121ce8..e3835355a5 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -9854,4 +9854,59 @@ private void
checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
fixture().withRelBuilderConfig(a -> a.withBloat(-1))
.relFn(relFn).withPlanner(planner).check();
}
+
+ @Test void testChainJoinDphypJoinReorder() {
+ HepProgram program = new HepProgramBuilder()
+ .addMatchOrder(HepMatchOrder.BOTTOM_UP)
+ .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
+ .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
+ .build();
+
+ sql("select emp.empno from "
+ + "emp, emp_address, dept, dept_nested "
+ + "where emp.deptno + emp_address.empno = dept.deptno +
dept_nested.deptno "
+ + "and emp.empno = emp_address.empno "
+ + "and dept.deptno = dept_nested.deptno")
+ .withPre(program)
+ .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE,
CoreRules.PROJECT_MERGE)
+ .check();
+ }
+
+ @Test void testStarJoinDphypJoinReorder() {
+ HepProgram program = new HepProgramBuilder()
+ .addMatchOrder(HepMatchOrder.BOTTOM_UP)
+ .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
+ .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
+ .build();
+
+ sql("select emp.empno from "
+ + "emp, emp_b, emp_address, dept, dept_nested "
+ + "where emp.empno = emp_b.empno "
+ + "and emp.empno = emp_address.empno "
+ + "and emp.deptno = dept.deptno "
+ + "and emp.deptno = dept_nested.deptno "
+ + "and emp_b.sal + emp_address.empno = dept.deptno +
dept_nested.deptno")
+ .withPre(program)
+ .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE,
CoreRules.PROJECT_MERGE)
+ .check();
+ }
+
+ @Test void testCycleJoinDphypJoinReorder() {
+ HepProgram program = new HepProgramBuilder()
+ .addMatchOrder(HepMatchOrder.BOTTOM_UP)
+ .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
+ .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
+ .build();
+
+ sql("select emp.empno from "
+ + "emp, emp_b, dept, dept_nested "
+ + "where emp.empno = emp_b.empno "
+ + "and emp_b.deptno = dept.deptno "
+ + "and dept.name = dept_nested.name "
+ + "and dept_nested.deptno = emp.deptno "
+ + "and emp.sal + emp_b.sal = dept.deptno + dept_nested.deptno")
+ .withPre(program)
+ .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE,
CoreRules.PROJECT_MERGE)
+ .check();
+ }
}
diff --git
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 4f63a49369..cde460b25f 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -1644,6 +1644,33 @@ case when cast(ename as double) < 5 then 0.0
<![CDATA[
LogicalProject(T=[CASE(<(CAST(CASE(>($1, 'abc'), $1,
null:VARCHAR(20))):DOUBLE, 5.0E0), 0.0E0:DOUBLE, CASE(IS NOT
NULL(CAST(CASE(>($1, 'abc'), $1, null:VARCHAR(20))):DOUBLE),
CAST(CAST(CASE(>($1, 'abc'), $1, null:VARCHAR(20))):DOUBLE):DOUBLE NOT NULL,
1.0E0:DOUBLE))])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testChainJoinDphypJoinReorder">
+ <Resource name="sql">
+ <![CDATA[select emp.empno from emp, emp_address, dept, dept_nested where
emp.deptno + emp_address.empno = dept.deptno + dept_nested.deptno and emp.empno
= emp_address.empno and dept.deptno = dept_nested.deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EMPNO=[$0])
+ HyperGraph(edges=[{0}——[INNER, =($0, $9)]——{1},{0, 1}——[INNER,
true]——{2},{0, 1, 2}——[INNER, =(+($7, $9), +($12, $14))]——{3},{2}——[INNER,
=($12, $14)]——{3}])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_ADDRESS]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EMPNO=[$9])
+ LogicalJoin(condition=[=(+($16, $6), +($4, $0))], joinType=[inner])
+ LogicalJoin(condition=[=($4, $0)], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalJoin(condition=[=($3, $0)], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_ADDRESS]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
@@ -2136,6 +2163,33 @@ and e1.deptno < 10 and d1.deptno < 15
and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)]]>
</Resource>
</TestCase>
+ <TestCase name="testCycleJoinDphypJoinReorder">
+ <Resource name="sql">
+ <![CDATA[select emp.empno from emp, emp_b, dept, dept_nested where
emp.empno = emp_b.empno and emp_b.deptno = dept.deptno and dept.name =
dept_nested.name and dept_nested.deptno = emp.deptno and emp.sal + emp_b.sal =
dept.deptno + dept_nested.deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EMPNO=[$0])
+ HyperGraph(edges=[{0}——[INNER, =($0, $9)]——{1},{1}——[INNER, =($16,
$19)]——{2},{2}——[INNER, =($20, $22)]——{3},{0}——[INNER, =($21, $7)]——{3},{0, 1,
2}——[INNER, =(+($5, $14), +($19, $21))]——{3}])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_B]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EMPNO=[$16])
+ LogicalJoin(condition=[AND(=($16, $6), =($0, $23), =(+($21, $11), +($4,
$0)))], joinType=[inner])
+ LogicalJoin(condition=[=($13, $4)], joinType=[inner])
+ LogicalJoin(condition=[=($5, $1)], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_B]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testDecorrelateAggWithConstantGroupKey">
<Resource name="sql">
<![CDATA[SELECT *
@@ -16716,6 +16770,36 @@ LogicalProject(EXPR$0=[SPLIT(null:NULL, null:NULL)])
<![CDATA[
LogicalProject(EXPR$0=[null:VARCHAR NOT NULL ARRAY])
LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testStarJoinDphypJoinReorder">
+ <Resource name="sql">
+ <![CDATA[select emp.empno from emp, emp_b, emp_address, dept,
dept_nested where emp.empno = emp_b.empno and emp.empno = emp_address.empno and
emp.deptno = dept.deptno and emp.deptno = dept_nested.deptno and emp_b.sal +
emp_address.empno = dept.deptno + dept_nested.deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EMPNO=[$0])
+ HyperGraph(edges=[{0}——[INNER, =($0, $9)]——{1},{0}——[INNER, =($0,
$19)]——{2},{0}——[INNER, =($7, $22)]——{3},{0}——[INNER, =($7, $24)]——{4},{1, 2,
3}——[INNER, =(+($14, $19), +($22, $24))]——{4}])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_B]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_ADDRESS]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EMPNO=[$19])
+ LogicalJoin(condition=[AND(=($19, $0), =(+($8, $0), +($17, $13)))],
joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_ADDRESS]])
+ LogicalJoin(condition=[=($16, $0)], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP_B]])
+ LogicalJoin(condition=[=($13, $0)], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT_NESTED]])
+ LogicalJoin(condition=[=($9, $0)], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>