mihaibudiu commented on code in PR #4392:
URL: https://github.com/apache/calcite/pull/4392#discussion_r2110407284


##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()

Review Comment:
   the tables in the paper have 7 operators, but it looks like you are omitting 
cross-product and group join (first and last). I would add this in the comment 
above.



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is
+  //  associative/left_asscom/right_asscom. See table2/3 in paper

Review Comment:
   this is table 2, the next 2 tables are from table 3 in the paper, why not be 
more explicit in the comment?



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is
+  //  associative/left_asscom/right_asscom. See table2/3 in paper
+  private static final boolean[][] ASSOCIATIVE_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] LEFT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {true,   true,   true,   true,   false},
+      /* anti-A  */ {true,   true,   true,   true,   false},
+      /* left-A  */ {true,   true,   true,   true,   false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] RIGHT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   false,  false,  false,  false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  /**
+   * Make conflict rules for join operator based on CD-C.
+   *
+   * @param leftSubEdges  left sub operators
+   * @param rightSubEdges right sub operators
+   * @param joinType      current join operator
+   * @return  a map from table set1 to table set2 that if set1 and the tables 
in the current join
+   * operator have an intersection, then set2 must be included in the current 
join operator
+   */
+  public static Map<Long, Long> makeConflictRules(

Review Comment:
   does this method belong to the hypergraph?
   it could take a join node as an argument.



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is
+  //  associative/left_asscom/right_asscom. See table2/3 in paper
+  private static final boolean[][] ASSOCIATIVE_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] LEFT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {true,   true,   true,   true,   false},
+      /* anti-A  */ {true,   true,   true,   true,   false},
+      /* left-A  */ {true,   true,   true,   true,   false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] RIGHT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   false,  false,  false,  false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  /**
+   * Make conflict rules for join operator based on CD-C.
+   *
+   * @param leftSubEdges  left sub operators
+   * @param rightSubEdges right sub operators
+   * @param joinType      current join operator
+   * @return  a map from table set1 to table set2 that if set1 and the tables 
in the current join
+   * operator have an intersection, then set2 must be included in the current 
join operator
+   */
+  public static Map<Long, Long> makeConflictRules(
+      List<HyperEdge> leftSubEdges,
+      List<HyperEdge> rightSubEdges,
+      JoinRelType joinType) {
+    Map<Long, Long> conflictRules = new HashMap<>();
+    for (HyperEdge leftSubEdge : leftSubEdges) {
+      if (!isAssociative(leftSubEdge.getJoinType(), joinType)) {
+        if (leftSubEdge.getLeftNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              leftSubEdge.getInitialRightNodeBits(),
+              leftSubEdge.getLeftNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              leftSubEdge.getInitialRightNodeBits(),
+              leftSubEdge.getInitialLeftNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+
+      }
+      if (!isLeftAsscom(leftSubEdge.getJoinType(), joinType)) {
+        if (leftSubEdge.getRightNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              leftSubEdge.getInitialLeftNodeBits(),
+              leftSubEdge.getRightNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              leftSubEdge.getInitialLeftNodeBits(),
+              leftSubEdge.getInitialRightNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+    }
+
+    for (HyperEdge rightSubEdge : rightSubEdges) {
+      if (!isAssociative(joinType, rightSubEdge.getJoinType())) {
+        if (rightSubEdge.getRightNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              rightSubEdge.getInitialLeftNodeBits(),
+              rightSubEdge.getRightNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              rightSubEdge.getInitialLeftNodeBits(),
+              rightSubEdge.getInitialRightNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+      if (!isRightAsscom(joinType, rightSubEdge.getJoinType())) {
+        if (rightSubEdge.getLeftNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              rightSubEdge.getInitialRightNodeBits(),
+              rightSubEdge.getLeftNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              rightSubEdge.getInitialRightNodeBits(),
+              rightSubEdge.getInitialLeftNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+    }
+    return conflictRules;
+  }
+
+  /**
+   * For conflict rule <code>T1 -&gt; T2</code>, if T1 and tes have 
intersection, we can add T2
+   * into tes and remove this rule. See section 5.5 in paper.
+   *
+   * @param tes                       tes
+   * @param conflictRulesAfterAbsorb  conflict rules after absorbing
+   * @param initialconflictRules      conflict rules before absorbing
+   * @return  tes after absorbing conflict rules
+   */
+  public static long absorbConflictRulesIntoTES(
+      long tes,
+      Map<Long, Long> conflictRulesAfterAbsorb,
+      Map<Long, Long> initialconflictRules) {
+    for (Map.Entry<Long, Long> rule : initialconflictRules.entrySet()) {
+      if (LongBitmap.isOverlap(tes, rule.getKey())) {
+        tes |= rule.getValue();
+        continue;
+      }
+      conflictRulesAfterAbsorb.put(rule.getKey(), rule.getValue());
+    }
+    return tes;
+  }
+
+  /**
+   * Check whether the operator is applicable through the conflict rule.
+   *
+   * @param subGraph  table set of the current join operator

Review Comment:
   there is no "current join operator" in this context, only the caller has it
   That's why this should also probably be a method of the hypergraph class.



##########
core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java:
##########
@@ -401,69 +453,67 @@ public RexNode extractJoinCond(RelNode left, RelNode 
right, List<HyperEdge> edge
     };
 
     for (HyperEdge edge : edges) {
-      RexNode inputRefCond = 
edge.getCondition().accept(inputName2InputRefShuttle);
+      RexNode inputRefCond = edge.getCondition().accept(shuttle);
       joinConds.add(inputRefCond);
     }
-    return RexUtil.composeConjunction(left.getCluster().getRexBuilder(), 
joinConds);
+    return RexUtil.composeConjunction(getCluster().getRexBuilder(), joinConds);
   }
 
   /**
-   * Before starting enumeration, add Project on every input, make all field 
name unique.
-   * Convert the HyperEdge condition from RexInputRef to RexInputFieldName
+   * Restore the projection order of the final result to the original plan.
+   *
+   * @param resultOrder the node order of the final result
+   * @param rowTypeList rowType of the final result
+   * @return  list of RexInputRef
    */
-  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++;
+  public List<RexNode> restoreProjectionOrder(
+      ImmutableList<Integer> resultOrder,
+      List<RelDataTypeField> rowTypeList) {
+    Map<Integer, Integer> relativePositionInNode = new HashMap<>();
+    int fieldCount = 0;
+    for (int resultIndex : resultOrder) {
+      relativePositionInNode.put(resultIndex, fieldCount);
+      if (!LongBitmap.isOverlap(notProjectInputs, 
LongBitmap.newBitmap(resultIndex))) {
+        fieldCount += inputs.get(resultIndex).getRowType().getFieldCount();
       }
-
-      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());
+    List<RexNode> projects = new ArrayList<>();
+    for (int inputIndex = 0; inputIndex < inputs.size(); inputIndex++) {
+      if (LongBitmap.isOverlap(notProjectInputs, 
LongBitmap.newBitmap(inputIndex))) {
+        continue;
       }
-    };
 
-    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);
+      for (int i = 0; i < inputs.get(inputIndex).getRowType().getFieldCount(); 
i++) {
+        Integer fieldOffset = relativePositionInNode.get(inputIndex);
+        if (fieldOffset == null) {
+          throw new DpHyp.DphypOrHyperGraphException(
+              "The result order loses the " + inputIndex + "-th input");

Review Comment:
   looses?
   is this an assertion failure?
   Then you should probably use the AssertionFailure exception.



##########
core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java:
##########
@@ -369,25 +388,58 @@ private static boolean isOverlapEdge(HyperEdge edge, long 
subset) {
     return joinType;
   }
 
-  public RexNode extractJoinCond(RelNode left, RelNode right, List<HyperEdge> 
edges) {
+  /**
+   * Restore join condition from hyper edges.

Review Comment:
   I think that a better description is "build an RexNode expression for the 
predicate corresponding to a set of hyperedges".



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * On the correct and complete enumeration of the core search space.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is

Review Comment:
   Instead of TODO I would write in the comment that the table here assumes 
that the predicate are assumed not to be null-rejecting.



##########
core/src/main/java/org/apache/calcite/rel/rules/DpHyp.java:
##########
@@ -37,7 +40,12 @@ public class DpHyp {
 
   private final HyperGraph hyperGraph;
 
-  private final HashMap<Long, RelNode> dpTable;
+  private final Map<Long, RelNode> dpTable;
+
+  // record the node order corresponding to the best subgraph, which is used 
to convert
+  // the RexNodeAndFieldIndex in hyperedge to the RexInputRef in join 
condition, and permute
+  // final result
+  private final Map<Long, ImmutableList<Integer>> resultInputOrder;

Review Comment:
   this map can become very large, is that right?
   is there any mechanism planned to control size explosion?



##########
core/src/main/java/org/apache/calcite/rel/rules/HyperEdge.java:
##########
@@ -20,42 +20,96 @@
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rex.RexNode;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Edge in HyperGraph, that represents a join predicate.
  */
 @Experimental
 public class HyperEdge {
 
-  private final long leftNodeBits;
+  // equivalent to the l-tes in CD-C paper
+  private final long leftEndpoint;

Review Comment:
   if these are all bitmaps, you should use a suitable bitmap class. Calcite 
already has a BitSet class. On second thought, using a class may be too 
expensive.



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is
+  //  associative/left_asscom/right_asscom. See table2/3 in paper
+  private static final boolean[][] ASSOCIATIVE_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] LEFT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {true,   true,   true,   true,   false},
+      /* anti-A  */ {true,   true,   true,   true,   false},
+      /* left-A  */ {true,   true,   true,   true,   false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] RIGHT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   false,  false,  false,  false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  /**
+   * Make conflict rules for join operator based on CD-C.
+   *
+   * @param leftSubEdges  left sub operators
+   * @param rightSubEdges right sub operators
+   * @param joinType      current join operator
+   * @return  a map from table set1 to table set2 that if set1 and the tables 
in the current join
+   * operator have an intersection, then set2 must be included in the current 
join operator
+   */
+  public static Map<Long, Long> makeConflictRules(
+      List<HyperEdge> leftSubEdges,
+      List<HyperEdge> rightSubEdges,
+      JoinRelType joinType) {
+    Map<Long, Long> conflictRules = new HashMap<>();

Review Comment:
   Can you define a class for a ConflictRule, even if it only stores a 
`HashMap<Long,Long>` inside?
   The documentation and methods of this class will make it much clearer about 
what about the fact that these Longs are bitmaps. Also, these ConflictRules 
should point to the hypergraph, and have nice toString() methods which 
translate indexes in the bitmap into operators in the plan.



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is
+  //  associative/left_asscom/right_asscom. See table2/3 in paper
+  private static final boolean[][] ASSOCIATIVE_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] LEFT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {true,   true,   true,   true,   false},
+      /* anti-A  */ {true,   true,   true,   true,   false},
+      /* left-A  */ {true,   true,   true,   true,   false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] RIGHT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   false,  false,  false,  false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  /**
+   * Make conflict rules for join operator based on CD-C.
+   *
+   * @param leftSubEdges  left sub operators
+   * @param rightSubEdges right sub operators
+   * @param joinType      current join operator
+   * @return  a map from table set1 to table set2 that if set1 and the tables 
in the current join
+   * operator have an intersection, then set2 must be included in the current 
join operator
+   */
+  public static Map<Long, Long> makeConflictRules(
+      List<HyperEdge> leftSubEdges,
+      List<HyperEdge> rightSubEdges,
+      JoinRelType joinType) {
+    Map<Long, Long> conflictRules = new HashMap<>();
+    for (HyperEdge leftSubEdge : leftSubEdges) {
+      if (!isAssociative(leftSubEdge.getJoinType(), joinType)) {
+        if (leftSubEdge.getLeftNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              leftSubEdge.getInitialRightNodeBits(),
+              leftSubEdge.getLeftNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              leftSubEdge.getInitialRightNodeBits(),
+              leftSubEdge.getInitialLeftNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+
+      }
+      if (!isLeftAsscom(leftSubEdge.getJoinType(), joinType)) {
+        if (leftSubEdge.getRightNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              leftSubEdge.getInitialLeftNodeBits(),
+              leftSubEdge.getRightNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              leftSubEdge.getInitialLeftNodeBits(),
+              leftSubEdge.getInitialRightNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+    }
+
+    for (HyperEdge rightSubEdge : rightSubEdges) {
+      if (!isAssociative(joinType, rightSubEdge.getJoinType())) {
+        if (rightSubEdge.getRightNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              rightSubEdge.getInitialLeftNodeBits(),
+              rightSubEdge.getRightNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              rightSubEdge.getInitialLeftNodeBits(),
+              rightSubEdge.getInitialRightNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+      if (!isRightAsscom(joinType, rightSubEdge.getJoinType())) {
+        if (rightSubEdge.getLeftNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              rightSubEdge.getInitialRightNodeBits(),
+              rightSubEdge.getLeftNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              rightSubEdge.getInitialRightNodeBits(),
+              rightSubEdge.getInitialLeftNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+    }
+    return conflictRules;
+  }
+
+  /**
+   * For conflict rule <code>T1 -&gt; T2</code>, if T1 and tes have 
intersection, we can add T2
+   * into tes and remove this rule. See section 5.5 in paper.
+   *
+   * @param tes                       tes

Review Comment:
   at least here you should spell Total Eligibility Set.



##########
core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java:
##########
@@ -401,69 +453,67 @@ public RexNode extractJoinCond(RelNode left, RelNode 
right, List<HyperEdge> edge
     };
 
     for (HyperEdge edge : edges) {
-      RexNode inputRefCond = 
edge.getCondition().accept(inputName2InputRefShuttle);
+      RexNode inputRefCond = edge.getCondition().accept(shuttle);
       joinConds.add(inputRefCond);
     }
-    return RexUtil.composeConjunction(left.getCluster().getRexBuilder(), 
joinConds);
+    return RexUtil.composeConjunction(getCluster().getRexBuilder(), joinConds);
   }
 
   /**
-   * Before starting enumeration, add Project on every input, make all field 
name unique.
-   * Convert the HyperEdge condition from RexInputRef to RexInputFieldName
+   * Restore the projection order of the final result to the original plan.
+   *
+   * @param resultOrder the node order of the final result
+   * @param rowTypeList rowType of the final result
+   * @return  list of RexInputRef
    */
-  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++;
+  public List<RexNode> restoreProjectionOrder(
+      ImmutableList<Integer> resultOrder,
+      List<RelDataTypeField> rowTypeList) {
+    Map<Integer, Integer> relativePositionInNode = new HashMap<>();
+    int fieldCount = 0;
+    for (int resultIndex : resultOrder) {
+      relativePositionInNode.put(resultIndex, fieldCount);
+      if (!LongBitmap.isOverlap(notProjectInputs, 
LongBitmap.newBitmap(resultIndex))) {
+        fieldCount += inputs.get(resultIndex).getRowType().getFieldCount();
       }
-
-      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());
+    List<RexNode> projects = new ArrayList<>();
+    for (int inputIndex = 0; inputIndex < inputs.size(); inputIndex++) {
+      if (LongBitmap.isOverlap(notProjectInputs, 
LongBitmap.newBitmap(inputIndex))) {
+        continue;
       }
-    };
 
-    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);
+      for (int i = 0; i < inputs.get(inputIndex).getRowType().getFieldCount(); 
i++) {
+        Integer fieldOffset = relativePositionInNode.get(inputIndex);
+        if (fieldOffset == null) {
+          throw new DpHyp.DphypOrHyperGraphException(
+              "The result order loses the " + inputIndex + "-th input");
+        }
+        int inputRef = i + fieldOffset;
+        projects.add(
+            new RexInputRef(inputRef, rowTypeList.get(inputRef).getType()));
+      }
     }
+    return projects;
   }
 
   /**
    * 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.
+   * so use node index and relative position of field in node replace 
RexInputRef.
+   * When build hyper graph, convert RexInputRef to RexNodeAndFieldIndex.
+   * When connect csgcmp to Join, convert RexNodeAndFieldIndex to RexInputRef.
    */
-  private static class RexInputFieldName extends RexVariable {
+  static class RexNodeAndFieldIndex extends RexVariable {

Review Comment:
   why is this a static class if the nodeIndex depends on the hypergraph?
   Should be instead an inner class of the HyperGraph?
   Then you could access the RexNode too.



##########
core/src/main/java/org/apache/calcite/rel/rules/DpHyp.java:
##########
@@ -173,42 +180,86 @@ private void enumerateCmpRec(long csg, long cmp, long 
forbidden) {
   private void emitCsgCmp(long csg, long cmp, List<HyperEdge> edges) {
     RelNode child1 = dpTable.get(csg);
     RelNode child2 = dpTable.get(cmp);
+    ImmutableList csgOrder = resultInputOrder.get(csg);
+    ImmutableList cmpOrder = resultInputOrder.get(cmp);
     if (child1 == null || child2 == null) {
-      throw new IllegalArgumentException(
+      throw new DphypOrHyperGraphException(
           "csg and cmp were not enumerated in the previous dp process");
     }
+    if (csgOrder == null || cmpOrder == null) {
+      throw new DphypOrHyperGraphException("Lost the vertex order of csg or 
cmp");
+    }
 
     JoinRelType joinType = hyperGraph.extractJoinType(edges);
     if (joinType == null) {
       return;
     }
-    RexNode joinCond1 = hyperGraph.extractJoinCond(child1, child2, edges);
+    if (!ConflictDetectionHelper.applicable(csg | cmp, edges)) {
+      return;
+    }
+
+    ImmutableList<Integer> unionOrder = ImmutableList.<Integer>builder()
+        .addAll(csgOrder)
+        .addAll(cmpOrder)
+        .build();
+    RexNode joinCond1 = hyperGraph.extractJoinCond(unionOrder, 
csgOrder.size(), edges);
     RelNode newPlan1 = builder
         .push(child1)
         .push(child2)
         .join(joinType, joinCond1)
         .build();
+    RelNode winPlan = newPlan1;
+    ImmutableList<Integer> winOrder = ImmutableList.copyOf(unionOrder);
 
-    // 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);
+    if (ConflictDetectionHelper.isCommutative(joinType)) {
+      // swap left and right
+      unionOrder = ImmutableList.<Integer>builder()
+          .addAll(cmpOrder)
+          .addAll(csgOrder)
+          .build();
+      RexNode joinCond2 = hyperGraph.extractJoinCond(unionOrder, 
cmpOrder.size(), edges);
+      RelNode newPlan2 = builder
+          .push(child2)
+          .push(child1)
+          .join(joinType, joinCond2)
+          .build();
+      winPlan = chooseBetterPlan(winPlan, newPlan2);
+      if (winPlan.equals(newPlan2)) {
+        winOrder = ImmutableList.copyOf(unionOrder);
+      }
+    }
 
     RelNode oriPlan = dpTable.get(csg | cmp);
     if (oriPlan != null) {
       winPlan = chooseBetterPlan(winPlan, oriPlan);
+      if (winPlan.equals(oriPlan)) {
+        winOrder = resultInputOrder.get(csg | cmp);
+      }
     }
+    assert winOrder != null;
     dpTable.put(csg | cmp, winPlan);
+    resultInputOrder.put(csg | cmp, winOrder);
   }
 
   public @Nullable RelNode getBestPlan() {
     int size = hyperGraph.getInputs().size();
     long wholeGraph = LongBitmap.newBitmapBetween(0, size);
-    return dpTable.get(wholeGraph);
+    RelNode orderedJoin = dpTable.get(wholeGraph);
+    if (orderedJoin == null) {
+      return null;
+    }
+    ImmutableList<Integer> resultOrder = resultInputOrder.get(wholeGraph);
+    if (resultOrder == null) {

Review Comment:
   can this happen for legal programs, or this is essentially an assertion 
failure?



##########
core/src/main/java/org/apache/calcite/rel/rules/JoinToHyperGraphRule.java:
##########
@@ -70,106 +75,205 @@ protected JoinToHyperGraphRule(Config config) {
       RelOptUtil.decomposeConjunction(origJoin.getCondition(), joinConds);
     }
 
-    // when right is HyperGraph, need shift the leftNodeBit, rightNodeBit, 
condition of HyperEdge
+    // when right is HyperGraph, need shift fields related to bitmap 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(
+      notProjectInputs |= ((HyperGraph) left).getNotProjectInputs();
+      notProjectInputs |= ((HyperGraph) right).getNotProjectInputs() << 
leftNodeCount;
+
+      leftSubEdges.addAll(((HyperGraph) left).getEdges());
+      rightSubEdges.addAll(
           ((HyperGraph) right).getEdges().stream()
-              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount, 
leftFieldCount))
+              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount))
               .collect(Collectors.toList()));
     } else if (left instanceof HyperGraph) {
       leftNodeCount = left.getInputs().size();
       inputs.addAll(left.getInputs());
       inputs.add(right);
 
-      edges.addAll(((HyperGraph) left).getEdges());
+      notProjectInputs |= ((HyperGraph) left).getNotProjectInputs();
+
+      leftSubEdges.addAll(((HyperGraph) left).getEdges());
     } else if (right instanceof HyperGraph) {
       leftNodeCount = 1;
       inputs.add(left);
       inputs.addAll(right.getInputs());
 
-      edges.addAll(
+      notProjectInputs |= ((HyperGraph) right).getNotProjectInputs() << 
leftNodeCount;
+
+      rightSubEdges.addAll(
           ((HyperGraph) right).getEdges().stream()
-              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount, 
leftFieldCount))
+              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount))
               .collect(Collectors.toList()));
     } else {
       leftNodeCount = 1;
       inputs.add(left);
       inputs.add(right);
     }
 
-    HashMap<Integer, Integer> fieldIndexToNodeIndexMap = new HashMap<>();
+    // calculate conflict rules
+    Map<Long, Long> conflictRules =
+        ConflictDetectionHelper.makeConflictRules(
+            leftSubEdges,
+            rightSubEdges,
+            origJoin.getJoinType());
+    leftSubEdges.addAll(rightSubEdges);
+
+    Map<Integer, Integer> fieldIndexToNodeIndexMap = new HashMap<>();
+    // the map from input index to the count of fields before it, used to 
convert RexInputRef to
+    // RexNodeAndFieldIndex
+    Map<Integer, Integer> relativePositionInNode = new HashMap<>();
     int fieldCount = 0;
     for (int i = 0; i < inputs.size(); i++) {
+      if (LongBitmap.isOverlap(notProjectInputs, LongBitmap.newBitmap(i))) {
+        continue;
+      }
+      relativePositionInNode.put(i, fieldCount);
       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;
+      long leftEndpoint;
+      long rightEndpoint;
+      long leftNodeUsedInPredicate;
+      long rightNodeUsedInPredicate;
+      long initialLeftNodeBits = LongBitmap.newBitmapBetween(0, leftNodeCount);
+      long initialRightNodeBits = LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
       List<Integer> leftRefs = new ArrayList<>();
       List<Integer> rightRefs = new ArrayList<>();
 
-      RexVisitorImpl visitor = new RexVisitorImpl<Void>(true) {
-        @Override public Void visitInputRef(RexInputRef inputRef) {
+      RexShuttle shuttle = new RexShuttle() {
+        @Override public RexNode 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());
+            throw new DpHyp.DphypOrHyperGraphException("When build hyper 
graph, RexInputRef refers "
+                + "a dummy field: " + inputRef + ", rowType is: " + 
origJoin.getRowType());
           }
           if (nodeIndex < leftNodeCount) {
             leftRefs.add(nodeIndex);
           } else {
             rightRefs.add(nodeIndex);
           }
-          return null;
+          Integer fieldOffset = relativePositionInNode.get(nodeIndex);
+          if (fieldOffset == null) {
+            throw new DpHyp.DphypOrHyperGraphException("When build hyper 
graph, failed to map "
+                + "input index to field count before it");
+          }
+          int fieldIndex = inputRef.getIndex() - fieldOffset;
+          if (fieldIndex < 0) {
+            throw new DpHyp.DphypOrHyperGraphException("When build hyper 
graph, failed to convert "
+                + "the input ref to the relative position of the field in the 
input");
+          }
+          return new HyperGraph.RexNodeAndFieldIndex(
+              nodeIndex,
+              fieldIndex,
+              inputRef.getName(),
+              inputRef.getType());
         }
       };
-      joinCond.accept(visitor);
+      RexNode hyperEdgeCondition = joinCond.accept(shuttle);
 
-      // when cartesian product, make it to complex hyper edge
+      Map<Long, Long> conflictRulesAfterAbsorb = new HashMap<>();
+      leftNodeUsedInPredicate = LongBitmap.newBitmapFromList(leftRefs);
+      rightNodeUsedInPredicate = LongBitmap.newBitmapFromList(rightRefs);
       if (leftRefs.isEmpty() || rightRefs.isEmpty()) {
-        leftNodeBits = LongBitmap.newBitmapBetween(0, leftNodeCount);
-        rightNodeBits = LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
+        // when cartesian product or degenerate predicate, a complex hyperedge 
is generated to fix
+        // current join operator without exploring more possibilities. See 
section 6.2 in CD-C paper
+        leftEndpoint = LongBitmap.newBitmapBetween(0, leftNodeCount);
+        rightEndpoint = LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
       } else {
-        leftNodeBits = LongBitmap.newBitmapFromList(leftRefs);
-        rightNodeBits = LongBitmap.newBitmapFromList(rightRefs);
+        // simplify conflict rules. See section 5.5 in CD-C paper
+        long tes =
+            ConflictDetectionHelper.absorbConflictRulesIntoTES(
+                leftNodeUsedInPredicate | rightNodeUsedInPredicate,
+                conflictRulesAfterAbsorb,
+                conflictRules);
+        rightEndpoint = tes & initialRightNodeBits;
+        leftEndpoint = tes & ~rightEndpoint;
       }
-      edges.add(
+      leftSubEdges.add(
           new HyperEdge(
-              leftNodeBits,
-              rightNodeBits,
+              leftEndpoint,
+              rightEndpoint,
+              leftNodeUsedInPredicate,
+              rightNodeUsedInPredicate,
+              conflictRulesAfterAbsorb,
+              initialLeftNodeBits,
+              initialRightNodeBits,
               origJoin.getJoinType(),
-              joinCond));
+              hyperEdgeCondition));
+    }
+
+    if (!origJoin.getJoinType().projectsRight()) {
+      notProjectInputs |= LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
     }
     result =
         new HyperGraph(
             origJoin.getCluster(),
             origJoin.getTraitSet(),
             inputs,
-            edges,
+            notProjectInputs,
+            leftSubEdges,
             origJoin.getRowType());
 
     call.transformTo(result);
   }
 
-  private static HyperEdge adjustNodeBit(HyperEdge hyperEdge, int nodeOffset, 
int fieldOffset) {
-    RexNode newCondition = RexUtil.shift(hyperEdge.getCondition(), 
fieldOffset);
+  private static HyperEdge adjustNodeBit(HyperEdge hyperEdge, int nodeOffset) {

Review Comment:
   should this be a method of the HyperEdge class?



##########
core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java:
##########
@@ -401,69 +453,67 @@ public RexNode extractJoinCond(RelNode left, RelNode 
right, List<HyperEdge> edge
     };
 
     for (HyperEdge edge : edges) {
-      RexNode inputRefCond = 
edge.getCondition().accept(inputName2InputRefShuttle);
+      RexNode inputRefCond = edge.getCondition().accept(shuttle);
       joinConds.add(inputRefCond);
     }
-    return RexUtil.composeConjunction(left.getCluster().getRexBuilder(), 
joinConds);
+    return RexUtil.composeConjunction(getCluster().getRexBuilder(), joinConds);

Review Comment:
   I should have looked at the original hypergraph PR, but I haven't.
   It seems to me like the shape of predicates in all joins is restricted to be 
conjunctions of equalities involving simple field accesses. Is this right?



##########
core/src/main/java/org/apache/calcite/rel/rules/HyperGraph.java:
##########
@@ -401,69 +453,67 @@ public RexNode extractJoinCond(RelNode left, RelNode 
right, List<HyperEdge> edge
     };
 
     for (HyperEdge edge : edges) {
-      RexNode inputRefCond = 
edge.getCondition().accept(inputName2InputRefShuttle);
+      RexNode inputRefCond = edge.getCondition().accept(shuttle);
       joinConds.add(inputRefCond);
     }
-    return RexUtil.composeConjunction(left.getCluster().getRexBuilder(), 
joinConds);
+    return RexUtil.composeConjunction(getCluster().getRexBuilder(), joinConds);
   }
 
   /**
-   * Before starting enumeration, add Project on every input, make all field 
name unique.
-   * Convert the HyperEdge condition from RexInputRef to RexInputFieldName
+   * Restore the projection order of the final result to the original plan.

Review Comment:
   ensure that the fields produced by the reordered join are in the same order 
as in the original plan.



##########
core/src/main/java/org/apache/calcite/rel/rules/JoinToHyperGraphRule.java:
##########
@@ -70,106 +75,205 @@ protected JoinToHyperGraphRule(Config config) {
       RelOptUtil.decomposeConjunction(origJoin.getCondition(), joinConds);
     }
 
-    // when right is HyperGraph, need shift the leftNodeBit, rightNodeBit, 
condition of HyperEdge
+    // when right is HyperGraph, need shift fields related to bitmap 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(
+      notProjectInputs |= ((HyperGraph) left).getNotProjectInputs();
+      notProjectInputs |= ((HyperGraph) right).getNotProjectInputs() << 
leftNodeCount;
+
+      leftSubEdges.addAll(((HyperGraph) left).getEdges());
+      rightSubEdges.addAll(
           ((HyperGraph) right).getEdges().stream()
-              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount, 
leftFieldCount))
+              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount))
               .collect(Collectors.toList()));
     } else if (left instanceof HyperGraph) {
       leftNodeCount = left.getInputs().size();
       inputs.addAll(left.getInputs());
       inputs.add(right);
 
-      edges.addAll(((HyperGraph) left).getEdges());
+      notProjectInputs |= ((HyperGraph) left).getNotProjectInputs();
+
+      leftSubEdges.addAll(((HyperGraph) left).getEdges());
     } else if (right instanceof HyperGraph) {
       leftNodeCount = 1;
       inputs.add(left);
       inputs.addAll(right.getInputs());
 
-      edges.addAll(
+      notProjectInputs |= ((HyperGraph) right).getNotProjectInputs() << 
leftNodeCount;
+
+      rightSubEdges.addAll(
           ((HyperGraph) right).getEdges().stream()
-              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount, 
leftFieldCount))
+              .map(hyperEdge -> adjustNodeBit(hyperEdge, leftNodeCount))
               .collect(Collectors.toList()));
     } else {
       leftNodeCount = 1;
       inputs.add(left);
       inputs.add(right);
     }
 
-    HashMap<Integer, Integer> fieldIndexToNodeIndexMap = new HashMap<>();
+    // calculate conflict rules
+    Map<Long, Long> conflictRules =
+        ConflictDetectionHelper.makeConflictRules(
+            leftSubEdges,
+            rightSubEdges,
+            origJoin.getJoinType());
+    leftSubEdges.addAll(rightSubEdges);
+
+    Map<Integer, Integer> fieldIndexToNodeIndexMap = new HashMap<>();
+    // the map from input index to the count of fields before it, used to 
convert RexInputRef to
+    // RexNodeAndFieldIndex
+    Map<Integer, Integer> relativePositionInNode = new HashMap<>();
     int fieldCount = 0;
     for (int i = 0; i < inputs.size(); i++) {
+      if (LongBitmap.isOverlap(notProjectInputs, LongBitmap.newBitmap(i))) {
+        continue;
+      }
+      relativePositionInNode.put(i, fieldCount);
       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;
+      long leftEndpoint;
+      long rightEndpoint;
+      long leftNodeUsedInPredicate;
+      long rightNodeUsedInPredicate;
+      long initialLeftNodeBits = LongBitmap.newBitmapBetween(0, leftNodeCount);
+      long initialRightNodeBits = LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
       List<Integer> leftRefs = new ArrayList<>();
       List<Integer> rightRefs = new ArrayList<>();
 
-      RexVisitorImpl visitor = new RexVisitorImpl<Void>(true) {
-        @Override public Void visitInputRef(RexInputRef inputRef) {
+      RexShuttle shuttle = new RexShuttle() {
+        @Override public RexNode 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());
+            throw new DpHyp.DphypOrHyperGraphException("When build hyper 
graph, RexInputRef refers "
+                + "a dummy field: " + inputRef + ", rowType is: " + 
origJoin.getRowType());
           }
           if (nodeIndex < leftNodeCount) {
             leftRefs.add(nodeIndex);
           } else {
             rightRefs.add(nodeIndex);
           }
-          return null;
+          Integer fieldOffset = relativePositionInNode.get(nodeIndex);
+          if (fieldOffset == null) {
+            throw new DpHyp.DphypOrHyperGraphException("When build hyper 
graph, failed to map "
+                + "input index to field count before it");
+          }
+          int fieldIndex = inputRef.getIndex() - fieldOffset;
+          if (fieldIndex < 0) {
+            throw new DpHyp.DphypOrHyperGraphException("When build hyper 
graph, failed to convert "
+                + "the input ref to the relative position of the field in the 
input");
+          }
+          return new HyperGraph.RexNodeAndFieldIndex(
+              nodeIndex,
+              fieldIndex,
+              inputRef.getName(),
+              inputRef.getType());
         }
       };
-      joinCond.accept(visitor);
+      RexNode hyperEdgeCondition = joinCond.accept(shuttle);
 
-      // when cartesian product, make it to complex hyper edge
+      Map<Long, Long> conflictRulesAfterAbsorb = new HashMap<>();
+      leftNodeUsedInPredicate = LongBitmap.newBitmapFromList(leftRefs);
+      rightNodeUsedInPredicate = LongBitmap.newBitmapFromList(rightRefs);
       if (leftRefs.isEmpty() || rightRefs.isEmpty()) {
-        leftNodeBits = LongBitmap.newBitmapBetween(0, leftNodeCount);
-        rightNodeBits = LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
+        // when cartesian product or degenerate predicate, a complex hyperedge 
is generated to fix
+        // current join operator without exploring more possibilities. See 
section 6.2 in CD-C paper
+        leftEndpoint = LongBitmap.newBitmapBetween(0, leftNodeCount);
+        rightEndpoint = LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
       } else {
-        leftNodeBits = LongBitmap.newBitmapFromList(leftRefs);
-        rightNodeBits = LongBitmap.newBitmapFromList(rightRefs);
+        // simplify conflict rules. See section 5.5 in CD-C paper
+        long tes =
+            ConflictDetectionHelper.absorbConflictRulesIntoTES(
+                leftNodeUsedInPredicate | rightNodeUsedInPredicate,
+                conflictRulesAfterAbsorb,
+                conflictRules);
+        rightEndpoint = tes & initialRightNodeBits;
+        leftEndpoint = tes & ~rightEndpoint;
       }
-      edges.add(
+      leftSubEdges.add(
           new HyperEdge(
-              leftNodeBits,
-              rightNodeBits,
+              leftEndpoint,
+              rightEndpoint,
+              leftNodeUsedInPredicate,
+              rightNodeUsedInPredicate,
+              conflictRulesAfterAbsorb,
+              initialLeftNodeBits,
+              initialRightNodeBits,
               origJoin.getJoinType(),
-              joinCond));
+              hyperEdgeCondition));
+    }
+
+    if (!origJoin.getJoinType().projectsRight()) {
+      notProjectInputs |= LongBitmap.newBitmapBetween(leftNodeCount, 
inputs.size());
     }
     result =
         new HyperGraph(
             origJoin.getCluster(),
             origJoin.getTraitSet(),
             inputs,
-            edges,
+            notProjectInputs,
+            leftSubEdges,
             origJoin.getRowType());
 
     call.transformTo(result);
   }
 
-  private static HyperEdge adjustNodeBit(HyperEdge hyperEdge, int nodeOffset, 
int fieldOffset) {
-    RexNode newCondition = RexUtil.shift(hyperEdge.getCondition(), 
fieldOffset);
+  private static HyperEdge adjustNodeBit(HyperEdge hyperEdge, int nodeOffset) {
+    RexShuttle shiftNodeIndexShuttle = 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 HyperGraph.RexNodeAndFieldIndex) {
+            clonedOperand =
+                new HyperGraph.RexNodeAndFieldIndex(
+                    ((HyperGraph.RexNodeAndFieldIndex) operand).nodeIndex + 
nodeOffset,

Review Comment:
   you should cast the operand only once



##########
core/src/main/java/org/apache/calcite/rel/rules/ConflictDetectionHelper.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.rel.core.JoinRelType;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Conflict detection algorithm based on CD-C. More details are in paper:
+ * <a 
href="https://15721.courses.cs.cmu.edu/spring2019/papers/23-optimizer2/p493-moerkotte.pdf";>
+ *   On the correct and complete enumeration of the core search space</a>.
+ */
+public class ConflictDetectionHelper {
+
+  private ConflictDetectionHelper() {
+  }
+
+  private static final ImmutableMap<JoinRelType, Integer> INDEX_OF_TABLE =
+      ImmutableMap.<JoinRelType, Integer>builder()
+          .put(JoinRelType.INNER, 0).put(JoinRelType.SEMI, 
1).put(JoinRelType.ANTI, 2)
+          .put(JoinRelType.LEFT, 3).put(JoinRelType.FULL, 4).build();
+
+  // TODO: when special attribute is null rejecting, left/full join is
+  //  associative/left_asscom/right_asscom. See table2/3 in paper
+  private static final boolean[][] ASSOCIATIVE_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] LEFT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   true,   true,   true,   false},
+      /* semi-A  */ {true,   true,   true,   true,   false},
+      /* anti-A  */ {true,   true,   true,   true,   false},
+      /* left-A  */ {true,   true,   true,   true,   false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  private static final boolean[][] RIGHT_ASSCOM_TABLE = {
+      //            inner-B  semi-B  anti-B  left-B  full-B
+      /* inner-A */ {true,   false,  false,  false,  false},
+      /* semi-A  */ {false,  false,  false,  false,  false},
+      /* anti-A  */ {false,  false,  false,  false,  false},
+      /* left-A  */ {false,  false,  false,  false,  false},
+      /* full-A  */ {false,  false,  false,  false,  false}};
+
+  /**
+   * Make conflict rules for join operator based on CD-C.
+   *
+   * @param leftSubEdges  left sub operators
+   * @param rightSubEdges right sub operators
+   * @param joinType      current join operator
+   * @return  a map from table set1 to table set2 that if set1 and the tables 
in the current join
+   * operator have an intersection, then set2 must be included in the current 
join operator
+   */
+  public static Map<Long, Long> makeConflictRules(
+      List<HyperEdge> leftSubEdges,
+      List<HyperEdge> rightSubEdges,
+      JoinRelType joinType) {
+    Map<Long, Long> conflictRules = new HashMap<>();
+    for (HyperEdge leftSubEdge : leftSubEdges) {
+      if (!isAssociative(leftSubEdge.getJoinType(), joinType)) {
+        if (leftSubEdge.getLeftNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              leftSubEdge.getInitialRightNodeBits(),
+              leftSubEdge.getLeftNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              leftSubEdge.getInitialRightNodeBits(),
+              leftSubEdge.getInitialLeftNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+
+      }
+      if (!isLeftAsscom(leftSubEdge.getJoinType(), joinType)) {
+        if (leftSubEdge.getRightNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              leftSubEdge.getInitialLeftNodeBits(),
+              leftSubEdge.getRightNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              leftSubEdge.getInitialLeftNodeBits(),
+              leftSubEdge.getInitialRightNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+    }
+
+    for (HyperEdge rightSubEdge : rightSubEdges) {
+      if (!isAssociative(joinType, rightSubEdge.getJoinType())) {
+        if (rightSubEdge.getRightNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              rightSubEdge.getInitialLeftNodeBits(),
+              rightSubEdge.getRightNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              rightSubEdge.getInitialLeftNodeBits(),
+              rightSubEdge.getInitialRightNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+      if (!isRightAsscom(joinType, rightSubEdge.getJoinType())) {
+        if (rightSubEdge.getLeftNodeUsedInPredicate() != 0) {
+          conflictRules.merge(
+              rightSubEdge.getInitialRightNodeBits(),
+              rightSubEdge.getLeftNodeUsedInPredicate(),
+              (oldValue, newValue) -> oldValue | newValue);
+        } else {
+          conflictRules.merge(
+              rightSubEdge.getInitialRightNodeBits(),
+              rightSubEdge.getInitialLeftNodeBits(),
+              (oldValue, newValue) -> oldValue | newValue);
+        }
+      }
+    }
+    return conflictRules;
+  }
+
+  /**
+   * For conflict rule <code>T1 -&gt; T2</code>, if T1 and tes have 
intersection, we can add T2
+   * into tes and remove this rule. See section 5.5 in paper.
+   *
+   * @param tes                       tes

Review Comment:
   I would also make a class for TES, this will make it much easier to read the 
code.
   This then becomes a method of the TES class.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to