You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by li...@apache.org on 2022/03/04 13:24:18 UTC

[calcite] 14/41: [CALCITE-4967] Support SQL hints for temporal table join

This is an automated email from the ASF dual-hosted git repository.

liyafan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 9d02d458e3113915858d928b5fbccc7636c0644d
Author: Jing Zhang <be...@gmail.com>
AuthorDate: Wed Dec 29 16:11:10 2021 +0800

    [CALCITE-4967] Support SQL hints for temporal table join
    
    close apache/calcite#2664
---
 .../adapter/enumerable/EnumerableCorrelate.java    |  2 +-
 .../org/apache/calcite/adapter/jdbc/JdbcRules.java |  2 +-
 .../org/apache/calcite/rel/core/Correlate.java     | 24 ++++++++-
 .../org/apache/calcite/rel/core/RelFactories.java  | 12 +++--
 .../apache/calcite/rel/hint/HintPredicates.java    |  5 ++
 .../calcite/rel/hint/NodeTypeHintPredicate.java    |  8 ++-
 .../calcite/rel/logical/LogicalCorrelate.java      | 41 ++++++++++++--
 .../apache/calcite/rel/mutable/MutableRels.java    |  5 +-
 .../calcite/rel/rules/JoinToCorrelateRule.java     |  1 +
 .../sql2rel/RelStructuredTypeFlattener.java        |  1 +
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  2 +-
 .../java/org/apache/calcite/tools/RelBuilder.java  |  4 +-
 .../org/apache/calcite/test/RelBuilderTest.java    | 30 +++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.java   |  3 +-
 .../apache/calcite/test/SqlHintsConverterTest.java | 62 +++++++++++++++++++++-
 .../org/apache/calcite/test/SqlToRelTestBase.java  | 11 +++-
 .../apache/calcite/test/SqlHintsConverterTest.xml  | 26 +++++++++
 17 files changed, 218 insertions(+), 21 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java
index 6f88372..efb0267 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCorrelate.java
@@ -52,7 +52,7 @@ public class EnumerableCorrelate extends Correlate
       RelNode left, RelNode right,
       CorrelationId correlationId,
       ImmutableBitSet requiredColumns, JoinRelType joinType) {
-    super(cluster, traits, left, right, correlationId, requiredColumns,
+    super(cluster, traits, ImmutableList.of(), left, right, correlationId, requiredColumns,
         joinType);
   }
 
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
index 5b9f8b7..37fb747 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
@@ -127,7 +127,7 @@ public class JdbcRules {
       };
 
   static final RelFactories.CorrelateFactory CORRELATE_FACTORY =
-      (left, right, correlationId, requiredColumns, joinType) -> {
+      (left, right, hints, correlationId, requiredColumns, joinType) -> {
         throw new UnsupportedOperationException("JdbcCorrelate");
       };
 
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Correlate.java b/core/src/main/java/org/apache/calcite/rel/core/Correlate.java
index d7d3602..f5194a1 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Correlate.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Correlate.java
@@ -25,6 +25,8 @@ import org.apache.calcite.rel.BiRel;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
@@ -68,12 +70,13 @@ import static java.util.Objects.requireNonNull;
  *
  * @see CorrelationId
  */
-public abstract class Correlate extends BiRel {
+public abstract class Correlate extends BiRel implements Hintable {
   //~ Instance fields --------------------------------------------------------
 
   protected final CorrelationId correlationId;
   protected final ImmutableBitSet requiredColumns;
   protected final JoinRelType joinType;
+  protected final ImmutableList<RelHint> hints;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -91,6 +94,7 @@ public abstract class Correlate extends BiRel {
   protected Correlate(
       RelOptCluster cluster,
       RelTraitSet traitSet,
+      List<RelHint> hints,
       RelNode left,
       RelNode right,
       CorrelationId correlationId,
@@ -101,9 +105,23 @@ public abstract class Correlate extends BiRel {
     this.joinType = requireNonNull(joinType, "joinType");
     this.correlationId = requireNonNull(correlationId, "correlationId");
     this.requiredColumns = requireNonNull(requiredColumns, "requiredColumns");
+    this.hints = ImmutableList.copyOf(hints);
     assert isValid(Litmus.THROW, null);
   }
 
+  @Deprecated // to be removed before 2.0
+  protected Correlate(
+      RelOptCluster cluster,
+      RelTraitSet traitSet,
+      RelNode left,
+      RelNode right,
+      CorrelationId correlationId,
+      ImmutableBitSet requiredColumns,
+      JoinRelType joinType) {
+    this(cluster, traitSet, ImmutableList.of(), left, right,
+        correlationId, requiredColumns, joinType);
+  }
+
   /**
    * Creates a Correlate by parsing serialized output.
    *
@@ -235,4 +253,8 @@ public abstract class Correlate extends BiRel {
         rowCount /* generate results */ + leftRowCount /* scan left results */,
         0, 0).plus(rescanCost);
   }
+
+  @Override public ImmutableList<RelHint> getHints() {
+    return hints;
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
index d6ab518..0998c99 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
@@ -392,16 +392,18 @@ public class RelFactories {
    * <p>The result is typically a {@link Correlate}.
    */
   public interface CorrelateFactory {
+
     /**
      * Creates a correlate.
      *
      * @param left             Left input
      * @param right            Right input
+     * @param hints            Hints
      * @param correlationId    Variable name for the row of left input
      * @param requiredColumns  Required columns
      * @param joinType         Join type
      */
-    RelNode createCorrelate(RelNode left, RelNode right,
+    RelNode createCorrelate(RelNode left, RelNode right, List<RelHint> hints,
         CorrelationId correlationId, ImmutableBitSet requiredColumns,
         JoinRelType joinType);
   }
@@ -411,10 +413,10 @@ public class RelFactories {
    * {@link org.apache.calcite.rel.logical.LogicalCorrelate}.
    */
   private static class CorrelateFactoryImpl implements CorrelateFactory {
-    @Override public RelNode createCorrelate(RelNode left, RelNode right,
-        CorrelationId correlationId, ImmutableBitSet requiredColumns,
-        JoinRelType joinType) {
-      return LogicalCorrelate.create(left, right, correlationId,
+
+    @Override public RelNode createCorrelate(RelNode left, RelNode right, List<RelHint> hints,
+        CorrelationId correlationId, ImmutableBitSet requiredColumns, JoinRelType joinType) {
+      return LogicalCorrelate.create(left, right, hints, correlationId,
           requiredColumns, joinType);
     }
   }
diff --git a/core/src/main/java/org/apache/calcite/rel/hint/HintPredicates.java b/core/src/main/java/org/apache/calcite/rel/hint/HintPredicates.java
index 266ccf4..97e6bf3 100644
--- a/core/src/main/java/org/apache/calcite/rel/hint/HintPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/hint/HintPredicates.java
@@ -50,6 +50,11 @@ public abstract class HintPredicates {
   public static final HintPredicate CALC =
       new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.CALC);
 
+  /** A hint predicate that indicates a hint can only be used to
+   * {@link org.apache.calcite.rel.core.Correlate} nodes. */
+  public static final HintPredicate CORRELATE =
+      new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.CORRELATE);
+
   /**
    * Returns a composed hint predicate that represents a short-circuiting logical
    * AND of an array of hint predicates {@code hintPredicates}.  When evaluating the composed
diff --git a/core/src/main/java/org/apache/calcite/rel/hint/NodeTypeHintPredicate.java b/core/src/main/java/org/apache/calcite/rel/hint/NodeTypeHintPredicate.java
index 02cccc5..cdcd7ba 100644
--- a/core/src/main/java/org/apache/calcite/rel/hint/NodeTypeHintPredicate.java
+++ b/core/src/main/java/org/apache/calcite/rel/hint/NodeTypeHintPredicate.java
@@ -19,6 +19,7 @@ package org.apache.calcite.rel.hint;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Calc;
+import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.TableScan;
@@ -63,7 +64,12 @@ public class NodeTypeHintPredicate implements HintPredicate {
     /**
      * The hint would be propagated to the Calc nodes.
      */
-    CALC(Calc.class);
+    CALC(Calc.class),
+
+    /**
+     * The hint would be propagated to the Correlate nodes.
+     */
+    CORRELATE(Correlate.class);
 
     /** Relational expression clazz that the hint can apply to. */
     @SuppressWarnings("ImmutableEnumChecker")
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java b/core/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java
index d73ce12..2929f03 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java
@@ -25,8 +25,13 @@ import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.util.ImmutableBitSet;
 
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
 import static java.util.Objects.requireNonNull;
 
 /**
@@ -58,6 +63,7 @@ public final class LogicalCorrelate extends Correlate {
   public LogicalCorrelate(
       RelOptCluster cluster,
       RelTraitSet traitSet,
+      List<RelHint> hints,
       RelNode left,
       RelNode right,
       CorrelationId correlationId,
@@ -66,6 +72,7 @@ public final class LogicalCorrelate extends Correlate {
     super(
         cluster,
         traitSet,
+        hints,
         left,
         right,
         correlationId,
@@ -73,11 +80,25 @@ public final class LogicalCorrelate extends Correlate {
         joinType);
   }
 
+  @Deprecated // to be removed before 2.0
+  public LogicalCorrelate(
+      RelOptCluster cluster,
+      RelTraitSet traitSet,
+      RelNode left,
+      RelNode right,
+      CorrelationId correlationId,
+      ImmutableBitSet requiredColumns,
+      JoinRelType joinType) {
+    this(cluster, traitSet, ImmutableList.of(), left, right,
+        correlationId, requiredColumns, joinType);
+  }
+
   /**
    * Creates a LogicalCorrelate by parsing serialized output.
    */
   public LogicalCorrelate(RelInput input) {
-    this(input.getCluster(), input.getTraitSet(), input.getInputs().get(0),
+    this(input.getCluster(), input.getTraitSet(), ImmutableList.of(),
+        input.getInputs().get(0),
         input.getInputs().get(1),
         new CorrelationId(
             (Integer) requireNonNull(input.get("correlation"), "correlation")),
@@ -86,26 +107,38 @@ public final class LogicalCorrelate extends Correlate {
   }
 
   /** Creates a LogicalCorrelate. */
-  public static LogicalCorrelate create(RelNode left, RelNode right,
+  public static LogicalCorrelate create(RelNode left, RelNode right, List<RelHint> hints,
       CorrelationId correlationId, ImmutableBitSet requiredColumns,
       JoinRelType joinType) {
     final RelOptCluster cluster = left.getCluster();
     final RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE);
-    return new LogicalCorrelate(cluster, traitSet, left, right, correlationId,
+    return new LogicalCorrelate(cluster, traitSet, hints, left, right, correlationId,
         requiredColumns, joinType);
   }
 
+  @Deprecated // to be removed before 2.0
+  public static LogicalCorrelate create(RelNode left, RelNode right,
+      CorrelationId correlationId, ImmutableBitSet requiredColumns,
+      JoinRelType joinType) {
+    return create(left, right, ImmutableList.of(), correlationId, requiredColumns, joinType);
+  }
+
   //~ Methods ----------------------------------------------------------------
 
   @Override public LogicalCorrelate copy(RelTraitSet traitSet,
       RelNode left, RelNode right, CorrelationId correlationId,
       ImmutableBitSet requiredColumns, JoinRelType joinType) {
     assert traitSet.containsIfApplicable(Convention.NONE);
-    return new LogicalCorrelate(getCluster(), traitSet, left, right,
+    return new LogicalCorrelate(getCluster(), traitSet, hints, left, right,
         correlationId, requiredColumns, joinType);
   }
 
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalCorrelate(getCluster(), traitSet, hintList, left, right,
+        correlationId, requiredColumns, joinType);
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java b/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
index ab8902e..708e881 100644
--- a/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
@@ -60,6 +60,8 @@ import org.apache.calcite.util.mapping.Mapping;
 import org.apache.calcite.util.mapping.MappingType;
 import org.apache.calcite.util.mapping.Mappings;
 
+import com.google.common.collect.ImmutableList;
+
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.AbstractList;
@@ -297,7 +299,8 @@ public abstract class MutableRels {
     case CORRELATE:
       final MutableCorrelate correlate = (MutableCorrelate) node;
       return LogicalCorrelate.create(fromMutable(correlate.getLeft(), relBuilder),
-          fromMutable(correlate.getRight(), relBuilder), correlate.correlationId,
+          fromMutable(correlate.getRight(), relBuilder),
+          ImmutableList.of(), correlate.correlationId,
           correlate.requiredColumns, correlate.joinType);
     case UNION:
       final MutableUnion union = (MutableUnion) node;
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java b/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java
index 154a200..296433e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinToCorrelateRule.java
@@ -124,6 +124,7 @@ public class JoinToCorrelateRule
     RelNode newRel =
         LogicalCorrelate.create(left,
             relBuilder.build(),
+            join.getHints(),
             correlationId,
             requiredColumns.build(),
             join.getJoinType());
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
index 2ad5955..f4bf28a 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
@@ -482,6 +482,7 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
     LogicalCorrelate newRel =
         LogicalCorrelate.create(getNewForOldRel(rel.getLeft()),
             getNewForOldRel(rel.getRight()),
+            rel.getHints(),
             rel.getCorrelationId(),
             newPos.build(),
             rel.getJoinType());
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 355614a..e6e79c7 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -2756,7 +2756,7 @@ public class SqlToRelConverter {
             .union(p.requiredColumns);
       }
 
-      return LogicalCorrelate.create(leftRel, innerRel,
+      return LogicalCorrelate.create(leftRel, innerRel, ImmutableList.of(),
           p.id, requiredCols, joinType);
     }
 
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 7d4b408..9c178c0 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -2791,7 +2791,7 @@ public class RelBuilder {
       }
       final ImmutableBitSet requiredColumns = RelOptUtil.correlationColumns(id, right.rel);
       join =
-          struct.correlateFactory.createCorrelate(left.rel, right.rel, id,
+          struct.correlateFactory.createCorrelate(left.rel, right.rel, ImmutableList.of(), id,
               requiredColumns, joinType);
     } else {
       RelNode join0 =
@@ -2836,7 +2836,7 @@ public class RelBuilder {
     Frame left = stack.pop();
 
     final RelNode correlate =
-        struct.correlateFactory.createCorrelate(left.rel, right.rel,
+        struct.correlateFactory.createCorrelate(left.rel, right.rel, ImmutableList.of(),
             correlationId, ImmutableBitSet.of(requiredOrdinals), joinType);
 
     final ImmutableList.Builder<Field> fields = ImmutableList.builder();
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 9f08de9..6887198 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -4403,6 +4403,10 @@ public class RelBuilderTest {
     final RelHint noHashJoinHint = RelHint.builder("NO_HASH_JOIN")
         .inheritPath(0)
         .build();
+    final RelHint hashJoinHint = RelHint.builder("USE_HASH_JOIN")
+        .hintOption("orders")
+        .hintOption("products_temporal")
+        .build();
     final RelBuilder builder = RelBuilder.create(config().build());
     // Equivalent SQL:
     //   SELECT *
@@ -4440,6 +4444,32 @@ public class RelBuilderTest {
         .hints(noHashJoinHint)
         .build();
     assertThat(root2, hasHints("[[NO_HASH_JOIN inheritPath:[0]]]"));
+
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM orders
+    //   JOIN products_temporal FOR SYSTEM_TIME AS OF orders.rowtime
+    //   ON orders.product = products_temporal.id
+    RelNode left = builder.scan("orders").build();
+    RelNode right = builder.scan("products_temporal").build();
+    RexNode period = builder.getRexBuilder().makeFieldAccess(
+        builder.getRexBuilder().makeCorrel(left.getRowType(), new CorrelationId(0)),
+        0);
+    RelNode root3 =
+        builder
+            .push(left)
+            .push(right)
+            .snapshot(period)
+            .correlate(
+                JoinRelType.INNER,
+                new CorrelationId(0),
+                builder.field(2, 0, "ROWTIME"),
+                builder.field(2, 0, "ID"),
+                builder.field(2, 0, "PRODUCT"))
+            .hints(hashJoinHint)
+            .build();
+    assertThat(root3,
+        hasHints("[[USE_HASH_JOIN inheritPath:[] options:[orders, products_temporal]]]"));
   }
 
   @Test void testHintsOnEmptyStack() {
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 864cb95..46e1ab2 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -1743,7 +1743,7 @@ class RelOptRulesTest extends RelOptTestBase {
           .project(b.field(0),
               b.getRexBuilder().makeFieldAccess(rexCorrel, 0)).build();
       LogicalCorrelate correlate = new LogicalCorrelate(left.getCluster(),
-          left.getTraitSet(), left, right, correlationId,
+          left.getTraitSet(), ImmutableList.of(), left, right, correlationId,
           ImmutableBitSet.of(0), type);
 
       b.push(correlate);
@@ -4061,6 +4061,7 @@ class RelOptRulesTest extends RelOptTestBase {
     CustomCorrelate customCorrelate = new CustomCorrelate(
         logicalCorrelate.getCluster(),
         logicalCorrelate.getTraitSet(),
+        logicalCorrelate.getHints(),
         logicalCorrelate.getLeft(),
         logicalCorrelate.getRight(),
         logicalCorrelate.getCorrelationId(),
diff --git a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
index 9084efa..c2c7783 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
@@ -39,8 +39,10 @@ import org.apache.calcite.rel.RelVisitor;
 import org.apache.calcite.rel.convert.ConverterRule;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Calc;
+import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinInfo;
+import org.apache.calcite.rel.core.Snapshot;
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.hint.HintPredicate;
 import org.apache.calcite.rel.hint.HintPredicates;
@@ -49,6 +51,7 @@ import org.apache.calcite.rel.hint.HintStrategyTable;
 import org.apache.calcite.rel.hint.Hintable;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rel.logical.LogicalCorrelate;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.rules.CoreRules;
@@ -75,8 +78,10 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.collection.IsIn.in;
@@ -145,6 +150,19 @@ class SqlHintsConverterTest extends SqlToRelTestBase {
     sql(sql).ok();
   }
 
+  @Test void testCorrelateHints() {
+    final String sql = "select /*+ use_hash_join (orders, products_temporal) */ stream *\n"
+        + "from orders join products_temporal for system_time as of orders.rowtime\n"
+        + "on orders.productid = products_temporal.productid and orders.orderId is not null";
+    sql(sql).ok();
+  }
+
+  @Test void testCrossCorrelateHints() {
+    final String sql = "select /*+ use_hash_join (orders, products_temporal) */ stream *\n"
+        + "from orders, products_temporal for system_time as of orders.rowtime";
+    sql(sql).ok();
+  }
+
   @Test void testHintsInSubQueryWithDecorrelation() {
     final String sql = "select /*+ resource(parallelism='3'), AGG_STRATEGY(TWO_PHASE) */\n"
         + "sum(e1.empno) from emp e1, dept d1\n"
@@ -693,6 +711,13 @@ class SqlHintsConverterTest extends SqlToRelTestBase {
         }
         return super.visit(aggregate);
       }
+
+      @Override public RelNode visit(LogicalCorrelate correlate) {
+        if (correlate.getHints().size() > 0) {
+          this.hintsCollect.add("Correlate:" + correlate.getHints().toString());
+        }
+        return super.visit(correlate);
+      }
     }
   }
 
@@ -771,7 +796,9 @@ class SqlHintsConverterTest extends SqlToRelTestBase {
                         + "allowed options: [ONE_PHASE, TWO_PHASE]",
                     hint.hintName)).build())
         .hintStrategy("use_hash_join",
-          HintPredicates.and(HintPredicates.JOIN, joinWithFixedTableName()))
+          HintPredicates.or(
+              HintPredicates.and(HintPredicates.CORRELATE, temporalJoinWithFixedTableName()),
+              HintPredicates.and(HintPredicates.JOIN, joinWithFixedTableName())))
         .hintStrategy("use_merge_join",
             HintStrategy.builder(
                 HintPredicates.and(HintPredicates.JOIN, joinWithFixedTableName()))
@@ -779,6 +806,39 @@ class SqlHintsConverterTest extends SqlToRelTestBase {
         .build();
     }
 
+    /** Returns a {@link HintPredicate} for temporal join with specified table references. */
+    private static HintPredicate temporalJoinWithFixedTableName() {
+      return (hint, rel) -> {
+        if (!(rel instanceof LogicalCorrelate)) {
+          return false;
+        }
+        LogicalCorrelate correlate = (LogicalCorrelate) rel;
+        Predicate<RelNode> isScan = r -> r instanceof TableScan;
+        if (!(isScan.test(correlate.getLeft()))) {
+          return false;
+        }
+        RelNode rightInput = correlate.getRight();
+        Predicate<RelNode> isSnapshotOnScan = r -> r instanceof Snapshot
+            && isScan.test(((Snapshot) r).getInput());
+        RelNode rightScan;
+        if (isSnapshotOnScan.test(rightInput)) {
+          rightScan = ((Snapshot) rightInput).getInput();
+        } else if (rightInput instanceof Filter
+            && isSnapshotOnScan.test(((Filter) rightInput).getInput())) {
+          rightScan = ((Snapshot) ((Filter) rightInput).getInput()).getInput();
+        } else {
+          // right child of correlate must be a snapshot on table scan directly or a Filter which
+          // input is snapshot on table scan
+          return false;
+        }
+        final List<String> tableNames = hint.listOptions;
+        final List<String> inputTables = Stream.of(correlate.getLeft(), rightScan)
+            .map(scan -> Util.last(scan.getTable().getQualifiedName()))
+            .collect(Collectors.toList());
+        return equalsStringList(inputTables, tableNames);
+      };
+    }
+
     /** Returns a {@link HintPredicate} for join with specified table references. */
     private static HintPredicate joinWithFixedTableName() {
       return (hint, rel) -> {
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
index 5f9a0ad..4feeb39 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
@@ -41,6 +41,7 @@ import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -1078,23 +1079,29 @@ public abstract class SqlToRelTestBase {
     public CustomCorrelate(
         RelOptCluster cluster,
         RelTraitSet traits,
+        List<RelHint> hints,
         RelNode left,
         RelNode right,
         CorrelationId correlationId,
         ImmutableBitSet requiredColumns,
         JoinRelType joinType) {
-      super(cluster, traits, left, right, correlationId, requiredColumns, joinType);
+      super(cluster, traits, hints, left, right, correlationId, requiredColumns, joinType);
     }
 
     @Override public Correlate copy(RelTraitSet traitSet,
         RelNode left, RelNode right, CorrelationId correlationId,
         ImmutableBitSet requiredColumns, JoinRelType joinType) {
-      return new CustomCorrelate(getCluster(), traitSet, left, right,
+      return new CustomCorrelate(getCluster(), traitSet, hints, left, right,
           correlationId, requiredColumns, joinType);
     }
 
     @Override public RelNode accept(RelShuttle shuttle) {
       return shuttle.visit(this);
     }
+
+    @Override public RelNode withHints(List<RelHint> hintList) {
+      return new CustomCorrelate(getCluster(), traitSet, hintList, left, right,
+          correlationId, requiredColumns, joinType);
+    }
   }
 }
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
index 4bb56c2..dbed18a 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
@@ -33,6 +33,32 @@ Project:[[RESOURCE inheritPath:[0, 0, 0, 0] options:{MEM=1024}]]
 ]]>
     </Resource>
   </TestCase>
+  <TestCase name="testCorrelateHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ use_hash_join (orders, products_temporal) */ stream *
+from orders join products_temporal for system_time as of orders.rowtime
+on orders.productid = products_temporal.productid and orders.orderId is not null]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[USE_HASH_JOIN inheritPath:[] options:[ORDERS, PRODUCTS_TEMPORAL]]]
+Correlate:[[USE_HASH_JOIN inheritPath:[0] options:[ORDERS, PRODUCTS_TEMPORAL]]]
+]]>
+    </Resource>
+  </TestCase>
+
+  <TestCase name="testCrossCorrelateHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ use_hash_join (orders, products_temporal) */ stream *
+from orders, products_temporal for system_time as of orders.rowtime]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[USE_HASH_JOIN inheritPath:[] options:[ORDERS, PRODUCTS_TEMPORAL]]]
+Correlate:[[USE_HASH_JOIN inheritPath:[0] options:[ORDERS, PRODUCTS_TEMPORAL]]]
+]]>
+    </Resource>
+  </TestCase>
   <TestCase name="testFourLevelNestedQueryHint">
     <Resource name="sql">
       <![CDATA[select /*+ index(idx1), no_hash_join */ * from emp /*+ index(empno) */