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

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


The following commit(s) were added to refs/heads/master by this push:
     new c98171da57 [CALCITE-5107] Support SQL hint for Filter, SetOp, Sort, 
Window, Values
c98171da57 is described below

commit c98171da5790d3d5ee7d2eb48a21d0080d0d68f3
Author: godfreyhe <[email protected]>
AuthorDate: Thu Apr 21 18:58:08 2022 +0800

    [CALCITE-5107] Support SQL hint for Filter, SetOp, Sort, Window, Values
    
    close apache/calcite#2775
---
 .../java/org/apache/calcite/rel/core/Filter.java   |  35 +++++-
 .../org/apache/calcite/rel/core/Intersect.java     |  16 ++-
 .../java/org/apache/calcite/rel/core/Minus.java    |  10 +-
 .../java/org/apache/calcite/rel/core/SetOp.java    |  25 ++++-
 .../java/org/apache/calcite/rel/core/Sort.java     |  60 +++++++---
 .../java/org/apache/calcite/rel/core/Union.java    |  13 ++-
 .../java/org/apache/calcite/rel/core/Values.java   |  36 +++++-
 .../java/org/apache/calcite/rel/core/Window.java   |  31 +++++-
 .../apache/calcite/rel/hint/HintPredicates.java    |  25 +++++
 .../calcite/rel/hint/NodeTypeHintPredicate.java    |  32 +++++-
 .../apache/calcite/rel/logical/LogicalFilter.java  |  33 +++++-
 .../calcite/rel/logical/LogicalIntersect.java      |  24 +++-
 .../apache/calcite/rel/logical/LogicalMinus.java   |  20 +++-
 .../apache/calcite/rel/logical/LogicalSort.java    |  20 +++-
 .../apache/calcite/rel/logical/LogicalUnion.java   |  23 +++-
 .../apache/calcite/rel/logical/LogicalValues.java  |  29 ++++-
 .../apache/calcite/rel/logical/LogicalWindow.java  |  31 +++++-
 .../org/apache/calcite/test/RelBuilderTest.java    |  71 ++++++++++--
 .../apache/calcite/test/SqlHintsConverterTest.java | 124 ++++++++++++++++++++-
 .../apache/calcite/test/SqlHintsConverterTest.xml  |  94 +++++++++++++++-
 20 files changed, 696 insertions(+), 56 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/core/Filter.java 
b/core/src/main/java/org/apache/calcite/rel/core/Filter.java
index ce4e0c0ec6..dbe44e4df0 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Filter.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Filter.java
@@ -24,6 +24,8 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.SingleRel;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexChecker;
@@ -34,6 +36,8 @@ import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.Litmus;
 
+import com.google.common.collect.ImmutableList;
+
 import org.apiguardian.api.API;
 import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -53,11 +57,13 @@ import static java.util.Objects.requireNonNull;
  *
  * @see org.apache.calcite.rel.logical.LogicalFilter
  */
-public abstract class Filter extends SingleRel {
+public abstract class Filter extends SingleRel implements Hintable {
   //~ Instance fields --------------------------------------------------------
 
   protected final RexNode condition;
 
+  protected final ImmutableList<RelHint> hints;
+
   //~ Constructors -----------------------------------------------------------
 
   /**
@@ -65,6 +71,7 @@ public abstract class Filter extends SingleRel {
    *
    * @param cluster   Cluster that this relational expression belongs to
    * @param traits    the traits of this rel
+   * @param hints     Hints for this node
    * @param child     input relational expression
    * @param condition boolean expression which determines whether a row is
    *                  allowed to pass
@@ -73,12 +80,31 @@ public abstract class Filter extends SingleRel {
   protected Filter(
       RelOptCluster cluster,
       RelTraitSet traits,
+      List<RelHint> hints,
       RelNode child,
       RexNode condition) {
     super(cluster, traits, child);
     this.condition = requireNonNull(condition, "condition");
     assert RexUtil.isFlat(condition) : "RexUtil.isFlat should be true for 
condition " + condition;
     assert isValid(Litmus.THROW, null);
+    this.hints = ImmutableList.copyOf(hints);
+  }
+
+  /**
+   * Creates a filter.
+   *
+   * @param cluster   Cluster that this relational expression belongs to
+   * @param traits    the traits of this rel
+   * @param child     input relational expression
+   * @param condition boolean expression which determines whether a row is
+   *                  allowed to pass
+   */
+  protected Filter(
+      RelOptCluster cluster,
+      RelTraitSet traits,
+      RelNode child,
+      RexNode condition) {
+    this(cluster, traits, ImmutableList.of(), child, condition);
   }
 
   /**
@@ -169,6 +195,7 @@ public abstract class Filter extends SingleRel {
     }
     Filter o = (Filter) obj;
     return traitSet.equals(o.traitSet)
+        && hints.equals(o.hints)
         && input.deepEquals(o.input)
         && condition.equals(o.condition)
         && getRowType().equalsSansFieldNames(o.getRowType());
@@ -176,6 +203,10 @@ public abstract class Filter extends SingleRel {
 
   @API(since = "1.24", status = API.Status.INTERNAL)
   protected int deepHashCode0() {
-    return Objects.hash(traitSet, input.deepHashCode(), condition);
+    return Objects.hash(traitSet, hints, input.deepHashCode(), condition);
+  }
+
+  @Override public ImmutableList<RelHint> getHints() {
+    return hints;
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Intersect.java 
b/core/src/main/java/org/apache/calcite/rel/core/Intersect.java
index c159353dad..e6ea2f6c24 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Intersect.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Intersect.java
@@ -20,9 +20,11 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.sql.SqlKind;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -33,6 +35,18 @@ import java.util.List;
  * performs set set intersection (implying no duplicates in the results).
  */
 public abstract class Intersect extends SetOp {
+  /**
+   * Creates an Intersect.
+   */
+  public Intersect(
+      RelOptCluster cluster,
+      RelTraitSet traits,
+      List<RelHint> hints,
+      List<RelNode> inputs,
+      boolean all) {
+    super(cluster, traits, hints, inputs, SqlKind.INTERSECT, all);
+  }
+
   /**
    * Creates an Intersect.
    */
@@ -41,7 +55,7 @@ public abstract class Intersect extends SetOp {
       RelTraitSet traits,
       List<RelNode> inputs,
       boolean all) {
-    super(cluster, traits, inputs, SqlKind.INTERSECT, all);
+    this(cluster, traits, Collections.emptyList(), inputs, all);
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Minus.java 
b/core/src/main/java/org/apache/calcite/rel/core/Minus.java
index 099443b458..3a68945acc 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Minus.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Minus.java
@@ -20,10 +20,12 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.sql.SqlKind;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -37,9 +39,15 @@ import java.util.List;
  * the results).
  */
 public abstract class Minus extends SetOp {
+
+  public Minus(RelOptCluster cluster, RelTraitSet traits, List<RelHint> hints,
+      List<RelNode> inputs, boolean all) {
+    super(cluster, traits, hints, inputs, SqlKind.EXCEPT, all);
+  }
+
   protected Minus(RelOptCluster cluster, RelTraitSet traits, List<RelNode> 
inputs,
       boolean all) {
-    super(cluster, traits, inputs, SqlKind.EXCEPT, all);
+    this(cluster, traits, Collections.emptyList(), inputs, all);
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/rel/core/SetOp.java 
b/core/src/main/java/org/apache/calcite/rel/core/SetOp.java
index 381277c9aa..1b5d57158d 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/SetOp.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/SetOp.java
@@ -24,6 +24,8 @@ import org.apache.calcite.rel.AbstractRelNode;
 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.type.RelDataType;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.util.Util;
@@ -32,25 +34,27 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
  * <code>SetOp</code> is an abstract base for relational set operators such
  * as UNION, MINUS (aka EXCEPT), and INTERSECT.
  */
-public abstract class SetOp extends AbstractRelNode {
+public abstract class SetOp extends AbstractRelNode implements Hintable {
   //~ Instance fields --------------------------------------------------------
 
   protected ImmutableList<RelNode> inputs;
   public final SqlKind kind;
   public final boolean all;
+  protected final ImmutableList<RelHint> hints;
 
   //~ Constructors -----------------------------------------------------------
 
   /**
    * Creates a SetOp.
    */
-  protected SetOp(RelOptCluster cluster, RelTraitSet traits,
+  protected SetOp(RelOptCluster cluster, RelTraitSet traits, List<RelHint> 
hints,
       List<RelNode> inputs, SqlKind kind, boolean all) {
     super(cluster, traits);
     Preconditions.checkArgument(kind == SqlKind.UNION
@@ -59,14 +63,23 @@ public abstract class SetOp extends AbstractRelNode {
     this.kind = kind;
     this.inputs = ImmutableList.copyOf(inputs);
     this.all = all;
+    this.hints = ImmutableList.copyOf(hints);
+  }
+
+  /**
+   * Creates a SetOp.
+   */
+  protected SetOp(RelOptCluster cluster, RelTraitSet traits,
+      List<RelNode> inputs, SqlKind kind, boolean all) {
+    this(cluster, traits, Collections.emptyList(), inputs, kind, all);
   }
 
   /**
    * Creates a SetOp by parsing serialized output.
    */
   protected SetOp(RelInput input) {
-    this(input.getCluster(), input.getTraitSet(), input.getInputs(),
-        SqlKind.UNION, input.getBoolean("all", false));
+    this(input.getCluster(), input.getTraitSet(), Collections.emptyList(),
+        input.getInputs(), SqlKind.UNION, input.getBoolean("all", false));
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -112,6 +125,10 @@ public abstract class SetOp extends AbstractRelNode {
     return rowType;
   }
 
+  @Override public ImmutableList<RelHint> getHints() {
+    return hints;
+  }
+
   /**
    * Returns whether all the inputs of this set operator have the same row
    * type as its output row.
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Sort.java 
b/core/src/main/java/org/apache/calcite/rel/core/Sort.java
index 1faad39d15..6297def223 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Sort.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Sort.java
@@ -28,14 +28,19 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.SingleRel;
+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.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.util.Util;
 
+import com.google.common.collect.ImmutableList;
+
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -43,15 +48,50 @@ import java.util.Objects;
  * Relational expression that imposes a particular sort order on its input
  * without otherwise changing its content.
  */
-public abstract class Sort extends SingleRel {
+public abstract class Sort extends SingleRel implements Hintable {
   //~ Instance fields --------------------------------------------------------
 
   public final RelCollation collation;
   public final @Nullable RexNode offset;
   public final @Nullable RexNode fetch;
+  protected final ImmutableList<RelHint> hints;
 
   //~ Constructors -----------------------------------------------------------
 
+  /**
+   * Creates a Sort.
+   *
+   * @param cluster   Cluster this relational expression belongs to
+   * @param traits    Traits
+   * @param hints     Hints for this node
+   * @param child     input relational expression
+   * @param collation array of sort specifications
+   * @param offset    Expression for number of rows to discard before returning
+   *                  first row
+   * @param fetch     Expression for number of rows to fetch
+   */
+  protected Sort(
+      RelOptCluster cluster,
+      RelTraitSet traits,
+      List<RelHint> hints,
+      RelNode child,
+      RelCollation collation,
+      @Nullable RexNode offset,
+      @Nullable RexNode fetch) {
+    super(cluster, traits, child);
+    this.collation = collation;
+    this.offset = offset;
+    this.fetch = fetch;
+    this.hints = ImmutableList.copyOf(hints);
+
+    assert traits.containsIfApplicable(collation)
+            : "traits=" + traits + ", collation=" + collation;
+    assert !(fetch == null
+            && offset == null
+            && collation.getFieldCollations().isEmpty())
+            : "trivial sort";
+  }
+
   /**
    * Creates a Sort.
    *
@@ -65,7 +105,7 @@ public abstract class Sort extends SingleRel {
       RelTraitSet traits,
       RelNode child,
       RelCollation collation) {
-    this(cluster, traits, child, collation, null, null);
+    this(cluster, traits, Collections.emptyList(), child, collation, null, 
null);
   }
 
   /**
@@ -86,17 +126,7 @@ public abstract class Sort extends SingleRel {
       RelCollation collation,
       @Nullable RexNode offset,
       @Nullable RexNode fetch) {
-    super(cluster, traits, child);
-    this.collation = collation;
-    this.offset = offset;
-    this.fetch = fetch;
-
-    assert traits.containsIfApplicable(collation)
-        : "traits=" + traits + ", collation=" + collation;
-    assert !(fetch == null
-        && offset == null
-        && collation.getFieldCollations().isEmpty())
-        : "trivial sort";
+    this(cluster, traits, Collections.emptyList(), child, collation, offset, 
fetch);
   }
 
   /**
@@ -252,4 +282,8 @@ public abstract class Sort extends SingleRel {
         ? ((RexLiteral) r).getValueAs(Double.class)
         : null;
   }
+
+  @Override public ImmutableList<RelHint> getHints() {
+    return hints;
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Union.java 
b/core/src/main/java/org/apache/calcite/rel/core/Union.java
index adf5d3f566..3aebe1e85b 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Union.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Union.java
@@ -20,10 +20,12 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.sql.SqlKind;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -38,9 +40,18 @@ public abstract class Union extends SetOp {
   protected Union(
       RelOptCluster cluster,
       RelTraitSet traits,
+      List<RelHint> hints,
       List<RelNode> inputs,
       boolean all) {
-    super(cluster, traits, inputs, SqlKind.UNION, all);
+    super(cluster, traits, hints, inputs, SqlKind.UNION, all);
+  }
+
+  protected Union(
+      RelOptCluster cluster,
+      RelTraitSet traits,
+      List<RelNode> inputs,
+      boolean all) {
+    super(cluster, traits, Collections.emptyList(), inputs, SqlKind.UNION, 
all);
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Values.java 
b/core/src/main/java/org/apache/calcite/rel/core/Values.java
index c671dba13a..c16954bd4d 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Values.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Values.java
@@ -24,6 +24,8 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.AbstractRelNode;
 import org.apache.calcite.rel.RelInput;
 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.rel.type.RelDataTypeField;
@@ -37,6 +39,7 @@ import com.google.common.collect.ImmutableList;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -45,10 +48,12 @@ import java.util.stream.Collectors;
  * Relational expression whose value is a sequence of zero or more literal row
  * values.
  */
-public abstract class Values extends AbstractRelNode {
+public abstract class Values extends AbstractRelNode implements Hintable {
 
   public static final Predicate<? super Values> IS_EMPTY_J = Values::isEmpty;
 
+  protected final ImmutableList<RelHint> hints;
+
   @SuppressWarnings("Guava")
   @Deprecated // to be removed before 2.0
   public static final com.google.common.base.Predicate<? super Values>
@@ -73,6 +78,7 @@ public abstract class Values extends AbstractRelNode {
    * call, otherwise bad things will happen.
    *
    * @param cluster Cluster that this relational expression belongs to
+   * @param hints   Hints for this node
    * @param rowType Row type for tuples produced by this rel
    * @param tuples  2-dimensional array of tuple values to be produced; outer
    *                list contains tuples; each inner list is one tuple; all
@@ -81,15 +87,39 @@ public abstract class Values extends AbstractRelNode {
   @SuppressWarnings("method.invocation.invalid")
   protected Values(
       RelOptCluster cluster,
+      List<RelHint> hints,
       RelDataType rowType,
       ImmutableList<ImmutableList<RexLiteral>> tuples,
       RelTraitSet traits) {
     super(cluster, traits);
     this.rowType = rowType;
     this.tuples = tuples;
+    this.hints = ImmutableList.copyOf(hints);
     assert assertRowType();
   }
 
+  /**
+   * Creates a new Values.
+   *
+   * <p>Note that tuples passed in become owned by
+   * this rel (without a deep copy), so caller must not modify them after this
+   * call, otherwise bad things will happen.
+   *
+   * @param cluster Cluster that this relational expression belongs to
+   * @param rowType Row type for tuples produced by this rel
+   * @param tuples  2-dimensional array of tuple values to be produced; outer
+   *                list contains tuples; each inner list is one tuple; all
+   *                tuples must be of same length, conforming to rowType
+   */
+  @SuppressWarnings("method.invocation.invalid")
+  protected Values(
+      RelOptCluster cluster,
+      RelDataType rowType,
+      ImmutableList<ImmutableList<RexLiteral>> tuples,
+      RelTraitSet traits) {
+    this(cluster, Collections.emptyList(), rowType, tuples, traits);
+  }
+
   /**
    * Creates a Values by parsing serialized output.
    */
@@ -199,4 +229,8 @@ public abstract class Values extends AbstractRelNode {
     }
     return relWriter;
   }
+
+  @Override public ImmutableList<RelHint> getHints() {
+    return hints;
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Window.java 
b/core/src/main/java/org/apache/calcite/rel/core/Window.java
index f13bd91785..e744f940ff 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Window.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Window.java
@@ -27,6 +27,8 @@ import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.SingleRel;
+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.rex.RexCall;
@@ -50,6 +52,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 import org.checkerframework.checker.nullness.qual.RequiresNonNull;
 
 import java.util.AbstractList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -67,27 +70,45 @@ import java.util.Objects;
  *
  * <p>Created by {@link org.apache.calcite.rel.rules.ProjectToWindowRule}.
  */
-public abstract class Window extends SingleRel {
+public abstract class Window extends SingleRel implements Hintable {
   public final ImmutableList<Group> groups;
   public final ImmutableList<RexLiteral> constants;
+  protected final ImmutableList<RelHint> hints;
 
   /**
    * Creates a window relational expression.
    *
    * @param cluster Cluster
    * @param traitSet Trait set
+   * @param hints   Hints for this node
    * @param input   Input relational expression
    * @param constants List of constants that are additional inputs
    * @param rowType Output row type
    * @param groups Windows
    */
-  protected Window(RelOptCluster cluster, RelTraitSet traitSet, RelNode input,
-      List<RexLiteral> constants, RelDataType rowType, List<Group> groups) {
+  protected Window(RelOptCluster cluster, RelTraitSet traitSet, List<RelHint> 
hints,
+      RelNode input, List<RexLiteral> constants, RelDataType rowType, 
List<Group> groups) {
     super(cluster, traitSet, input);
     this.constants = ImmutableList.copyOf(constants);
     assert rowType != null;
     this.rowType = rowType;
     this.groups = ImmutableList.copyOf(groups);
+    this.hints = ImmutableList.copyOf(hints);
+  }
+
+  /**
+   * Creates a window relational expression.
+   *
+   * @param cluster Cluster
+   * @param traitSet Trait set
+   * @param input   Input relational expression
+   * @param constants List of constants that are additional inputs
+   * @param rowType Output row type
+   * @param groups Windows
+   */
+  public Window(RelOptCluster cluster, RelTraitSet traitSet, RelNode input,
+      List<RexLiteral> constants, RelDataType rowType, List<Group> groups) {
+    this(cluster, traitSet, Collections.emptyList(), input, constants, 
rowType, groups);
   }
 
   @Override public boolean isValid(Litmus litmus, @Nullable Context context) {
@@ -428,4 +449,8 @@ public abstract class Window extends SingleRel {
       return super.clone(type, operands);
     }
   }
+
+  @Override public ImmutableList<RelHint> getHints() {
+    return hints;
+  }
 }
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 97e6bf33bb..f847982968 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
@@ -55,6 +55,31 @@ public abstract class HintPredicates {
   public static final HintPredicate CORRELATE =
       new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.CORRELATE);
 
+  /** A hint predicate that indicates a hint can only be used to
+   * {@link org.apache.calcite.rel.core.Filter} nodes. */
+  public static final HintPredicate FILTER =
+          new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.FILTER);
+
+  /** A hint predicate that indicates a hint can only be used to
+   * {@link org.apache.calcite.rel.core.SetOp} nodes. */
+  public static final HintPredicate SETOP =
+          new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.SETOP);
+
+  /** A hint predicate that indicates a hint can only be used to
+   * {@link org.apache.calcite.rel.core.Sort} nodes. */
+  public static final HintPredicate SORT =
+          new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.SORT);
+
+  /** A hint predicate that indicates a hint can only be used to
+   * {@link org.apache.calcite.rel.core.Values} nodes. */
+  public static final HintPredicate VALUES =
+      new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.VALUES);
+
+  /** A hint predicate that indicates a hint can only be used to
+   * {@link org.apache.calcite.rel.core.Window} nodes. */
+  public static final HintPredicate WINDOW =
+      new NodeTypeHintPredicate(NodeTypeHintPredicate.NodeType.WINDOW);
+
   /**
    * 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 cdcd7ba52d..225453d543 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
@@ -20,9 +20,14 @@ 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.Filter;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.SetOp;
+import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.core.Window;
 
 /**
  * A hint predicate that specifies which kind of relational
@@ -69,7 +74,32 @@ public class NodeTypeHintPredicate implements HintPredicate {
     /**
      * The hint would be propagated to the Correlate nodes.
      */
-    CORRELATE(Correlate.class);
+    CORRELATE(Correlate.class),
+
+    /**
+     * The hint would be propagated to the Filter nodes.
+     */
+    FILTER(Filter.class),
+
+    /**
+     * The hint would be propagated to the SetOp(Union, Intersect, Minus) 
nodes.
+     */
+    SETOP(SetOp.class),
+
+    /**
+     * The hint would be propagated to the Sort nodes.
+     */
+    SORT(Sort.class),
+
+    /**
+     * The hint would be propagated to the Values nodes.
+     */
+    VALUES(Values.class),
+
+    /**
+     * The hint would be propagated to the Window nodes.
+     */
+    WINDOW(Window.class);
 
     /** Relational expression clazz that the hint can apply to. */
     @SuppressWarnings("ImmutableEnumChecker")
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
index 9bc9903cec..829117c784 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
@@ -27,15 +27,18 @@ import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMdCollation;
 import org.apache.calcite.rel.metadata.RelMdDistribution;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexNode;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -63,13 +66,35 @@ public final class LogicalFilter extends Filter {
   public LogicalFilter(
       RelOptCluster cluster,
       RelTraitSet traitSet,
+      List<RelHint> hints,
       RelNode child,
       RexNode condition,
       ImmutableSet<CorrelationId> variablesSet) {
-    super(cluster, traitSet, child, condition);
+    super(cluster, traitSet, hints, child, condition);
     this.variablesSet = Objects.requireNonNull(variablesSet, "variablesSet");
   }
 
+  /**
+   * Creates a LogicalFilter.
+   *
+   * <p>Use {@link #create} unless you know what you're doing.
+   *
+   * @param cluster   Cluster that this relational expression belongs to
+   * @param child     Input relational expression
+   * @param condition Boolean expression which determines whether a row is
+   *                  allowed to pass
+   * @param variablesSet Correlation variables set by this relational 
expression
+   *                     to be used by nested expressions
+   */
+  public LogicalFilter(
+      RelOptCluster cluster,
+      RelTraitSet traitSet,
+      RelNode child,
+      RexNode condition,
+      ImmutableSet<CorrelationId> variablesSet) {
+    this(cluster, traitSet, ImmutableList.of(), child, condition, 
variablesSet);
+  }
+
   @Deprecated // to be removed before 2.0
   public LogicalFilter(
       RelOptCluster cluster,
@@ -123,7 +148,7 @@ public final class LogicalFilter extends Filter {
   @Override public LogicalFilter copy(RelTraitSet traitSet, RelNode input,
       RexNode condition) {
     assert traitSet.containsIfApplicable(Convention.NONE);
-    return new LogicalFilter(getCluster(), traitSet, input, condition,
+    return new LogicalFilter(getCluster(), traitSet, hints, input, condition,
         variablesSet);
   }
 
@@ -144,4 +169,8 @@ public final class LogicalFilter extends Filter {
   @Override public int deepHashCode() {
     return Objects.hash(deepHashCode0(), variablesSet);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalFilter(getCluster(), traitSet, hintList, input, 
condition, variablesSet);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalIntersect.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalIntersect.java
index 99ecf659df..f31f11292f 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalIntersect.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalIntersect.java
@@ -23,7 +23,9 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Intersect;
+import org.apache.calcite.rel.hint.RelHint;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -41,9 +43,23 @@ public final class LogicalIntersect extends Intersect {
   public LogicalIntersect(
       RelOptCluster cluster,
       RelTraitSet traitSet,
+      List<RelHint> hints,
       List<RelNode> inputs,
       boolean all) {
-    super(cluster, traitSet, inputs, all);
+    super(cluster, traitSet, hints, inputs, all);
+  }
+
+  /**
+   * Creates a LogicalIntersect.
+   *
+   * <p>Use {@link #create} unless you know what you're doing.
+   */
+  public LogicalIntersect(
+      RelOptCluster cluster,
+      RelTraitSet traitSet,
+      List<RelNode> inputs,
+      boolean all) {
+    this(cluster, traitSet, Collections.emptyList(), inputs, all);
   }
 
   @Deprecated // to be removed before 2.0
@@ -69,10 +85,14 @@ public final class LogicalIntersect extends Intersect {
 
   @Override public LogicalIntersect copy(RelTraitSet traitSet,
       List<RelNode> inputs, boolean all) {
-    return new LogicalIntersect(getCluster(), traitSet, inputs, all);
+    return new LogicalIntersect(getCluster(), traitSet, hints, inputs, all);
   }
 
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalIntersect(getCluster(), traitSet, hintList, inputs, all);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalMinus.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalMinus.java
index 357c403510..745d2174df 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalMinus.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalMinus.java
@@ -23,7 +23,9 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Minus;
+import org.apache.calcite.rel.hint.RelHint;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -33,6 +35,16 @@ import java.util.List;
 public final class LogicalMinus extends Minus {
   //~ Constructors -----------------------------------------------------------
 
+  /**
+   * Creates a LogicalMinus.
+   *
+   * <p>Use {@link #create} unless you know what you're doing.
+   */
+  public LogicalMinus(RelOptCluster cluster, RelTraitSet traitSet,
+      List<RelHint> hints, List<RelNode> inputs, boolean all) {
+    super(cluster, traitSet, hints, inputs, all);
+  }
+
   /**
    * Creates a LogicalMinus.
    *
@@ -40,7 +52,7 @@ public final class LogicalMinus extends Minus {
    */
   public LogicalMinus(RelOptCluster cluster, RelTraitSet traitSet,
       List<RelNode> inputs, boolean all) {
-    super(cluster, traitSet, inputs, all);
+    this(cluster, traitSet, Collections.emptyList(), inputs, all);
   }
 
   @Deprecated // to be removed before 2.0
@@ -69,10 +81,14 @@ public final class LogicalMinus extends Minus {
   @Override public LogicalMinus copy(RelTraitSet traitSet, List<RelNode> 
inputs,
       boolean all) {
     assert traitSet.containsIfApplicable(Convention.NONE);
-    return new LogicalMinus(getCluster(), traitSet, inputs, all);
+    return new LogicalMinus(getCluster(), traitSet, hints, inputs, all);
   }
 
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalMinus(getCluster(), traitSet, hintList, inputs, all);
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java
index c52acf80b4..31b8b4b006 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java
@@ -25,10 +25,14 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rex.RexNode;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Sub-class of {@link org.apache.calcite.rel.core.Sort} not
  * targeted at any particular engine or calling convention.
@@ -36,7 +40,12 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 public final class LogicalSort extends Sort {
   private LogicalSort(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, RelCollation collation, @Nullable RexNode offset, 
@Nullable RexNode fetch) {
-    super(cluster, traitSet, input, collation, offset, fetch);
+    this(cluster, traitSet, Collections.emptyList(), input, collation, offset, 
fetch);
+  }
+
+  private LogicalSort(RelOptCluster cluster, RelTraitSet traitSet, 
List<RelHint> hints,
+      RelNode input, RelCollation collation, @Nullable RexNode offset, 
@Nullable RexNode fetch) {
+    super(cluster, traitSet, hints, input, collation, offset, fetch);
     assert traitSet.containsIfApplicable(Convention.NONE);
   }
 
@@ -69,11 +78,16 @@ public final class LogicalSort extends Sort {
 
   @Override public Sort copy(RelTraitSet traitSet, RelNode newInput,
       RelCollation newCollation, @Nullable RexNode offset, @Nullable RexNode 
fetch) {
-    return new LogicalSort(getCluster(), traitSet, newInput, newCollation,
-        offset, fetch);
+    return new LogicalSort(getCluster(), traitSet, hints, newInput,
+        newCollation, offset, fetch);
   }
 
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalSort(getCluster(), traitSet, hintList,
+        input, collation, offset, fetch);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java
index a56ba11567..e8855c6568 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java
@@ -23,7 +23,9 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Union;
+import org.apache.calcite.rel.hint.RelHint;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -40,9 +42,22 @@ public final class LogicalUnion extends Union {
    */
   public LogicalUnion(RelOptCluster cluster,
       RelTraitSet traitSet,
+      List<RelHint> hints,
       List<RelNode> inputs,
       boolean all) {
-    super(cluster, traitSet, inputs, all);
+    super(cluster, traitSet, hints, inputs, all);
+  }
+
+  /**
+   * Creates a LogicalUnion.
+   *
+   * <p>Use {@link #create} unless you know what you're doing.
+   */
+  public LogicalUnion(RelOptCluster cluster,
+      RelTraitSet traitSet,
+      List<RelNode> inputs,
+      boolean all) {
+    this(cluster, traitSet, Collections.emptyList(), inputs, all);
   }
 
   @Deprecated // to be removed before 2.0
@@ -70,10 +85,14 @@ public final class LogicalUnion extends Union {
   @Override public LogicalUnion copy(
       RelTraitSet traitSet, List<RelNode> inputs, boolean all) {
     assert traitSet.containsIfApplicable(Convention.NONE);
-    return new LogicalUnion(getCluster(), traitSet, inputs, all);
+    return new LogicalUnion(getCluster(), traitSet, hints, inputs, all);
   }
 
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalUnion(getCluster(), traitSet, hintList, inputs, all);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java
index 11ea70f1b2..024f0112ed 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java
@@ -25,6 +25,7 @@ import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMdCollation;
 import org.apache.calcite.rel.metadata.RelMdDistribution;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
@@ -35,6 +36,7 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import com.google.common.collect.ImmutableList;
 
 import java.math.BigDecimal;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -50,6 +52,7 @@ public class LogicalValues extends Values {
    * <p>Use {@link #create} unless you know what you're doing.
    *
    * @param cluster Cluster that this relational expression belongs to
+   * @param hints   Hints for this node
    * @param rowType Row type for tuples produced by this rel
    * @param tuples  2-dimensional array of tuple values to be produced; outer
    *                list contains tuples; each inner list is one tuple; all
@@ -58,9 +61,29 @@ public class LogicalValues extends Values {
   public LogicalValues(
       RelOptCluster cluster,
       RelTraitSet traitSet,
+      List<RelHint> hints,
       RelDataType rowType,
       ImmutableList<ImmutableList<RexLiteral>> tuples) {
-    super(cluster, rowType, tuples, traitSet);
+    super(cluster, hints, rowType, tuples, traitSet);
+  }
+
+  /**
+   * Creates a LogicalValues.
+   *
+   * <p>Use {@link #create} unless you know what you're doing.
+   *
+   * @param cluster Cluster that this relational expression belongs to
+   * @param rowType Row type for tuples produced by this rel
+   * @param tuples  2-dimensional array of tuple values to be produced; outer
+   *                list contains tuples; each inner list is one tuple; all
+   *                tuples must be of same length, conforming to rowType
+   */
+  public LogicalValues(
+      RelOptCluster cluster,
+      RelTraitSet traitSet,
+      RelDataType rowType,
+      ImmutableList<ImmutableList<RexLiteral>> tuples) {
+    this(cluster, traitSet, Collections.emptyList(), rowType, tuples);
   }
 
   @Deprecated // to be removed before 2.0
@@ -121,4 +144,8 @@ public class LogicalValues extends Values {
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalValues(getCluster(), traitSet, hintList, getRowType(), 
tuples);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
index c53e03953a..2ebc48b858 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
@@ -23,6 +23,7 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Window;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
@@ -48,6 +49,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -66,15 +68,33 @@ public final class LogicalWindow extends Window {
    *
    * @param cluster Cluster
    * @param traitSet Trait set
+   * @param hints   Hints for this node
    * @param input   Input relational expression
    * @param constants List of constants that are additional inputs
    * @param rowType Output row type
    * @param groups Window groups
    */
   public LogicalWindow(RelOptCluster cluster, RelTraitSet traitSet,
-      RelNode input, List<RexLiteral> constants, RelDataType rowType,
-      List<Group> groups) {
-    super(cluster, traitSet, input, constants, rowType, groups);
+      List<RelHint> hints, RelNode input, List<RexLiteral> constants,
+      RelDataType rowType, List<Group> groups) {
+    super(cluster, traitSet, hints, input, constants, rowType, groups);
+  }
+
+  /**
+   * Creates a LogicalWindow.
+   *
+   * <p>Use {@link #create} unless you know what you're doing.
+   *
+   * @param cluster Cluster
+   * @param traitSet Trait set
+   * @param input   Input relational expression
+   * @param constants List of constants that are additional inputs
+   * @param rowType Output row type
+   * @param groups Window groups
+   */
+  public LogicalWindow(RelOptCluster cluster, RelTraitSet traitSet, RelNode 
input,
+      List<RexLiteral> constants, RelDataType rowType, List<Group> groups) {
+    this(cluster, traitSet, Collections.emptyList(), input, constants, 
rowType, groups);
   }
 
   @Override public LogicalWindow copy(RelTraitSet traitSet,
@@ -366,4 +386,9 @@ public final class LogicalWindow extends Window {
             aggWindow.getLowerBound(), aggWindow.getUpperBound());
     windowMap.put(windowKey, over);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    return new LogicalWindow(getCluster(), traitSet, hintList,
+        input, constants, getRowType(), groups);
+  }
 }
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 e174fae8e6..6f13e8736c 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -4554,17 +4554,74 @@ public class RelBuilderTest {
     final AssertionError error1 = assertThrows(
         AssertionError.class,
         () -> {
-          final RelBuilder builder = RelBuilder.create(config().build());
           // Equivalent SQL:
           //   SELECT *
           //   FROM emp
-          //   WHERE EMPNO = 124
+          //   MATCH_RECOGNIZE (
+          //     PARTITION BY deptno
+          //     ORDER BY empno asc
+          //     MEASURES
+          //       STRT.mgr as start_nw,
+          //       LAST(DOWN.mgr) as bottom_nw,
+          //     PATTERN (STRT DOWN+ UP+) WITHIN INTERVAL '5' SECOND
+          //     DEFINE
+          //       DOWN as DOWN.mgr < PREV(DOWN.mgr),
+          //       UP as UP.mgr > PREV(UP.mgr)
+          //   )
+          final RelBuilder builder = 
RelBuilder.create(config().build()).scan("EMP");
+          final RelDataTypeFactory typeFactory = builder.getTypeFactory();
+          final RelDataType intType = 
typeFactory.createSqlType(SqlTypeName.INTEGER);
+
+          RexNode pattern = builder.patternConcat(
+              builder.literal("STRT"),
+              builder.patternQuantify(builder.literal("DOWN"), 
builder.literal(1),
+                  builder.literal(-1), builder.literal(false)),
+              builder.patternQuantify(builder.literal("UP"), 
builder.literal(1),
+                  builder.literal(-1), builder.literal(false)));
+
+          ImmutableMap.Builder<String, RexNode> pdBuilder = new 
ImmutableMap.Builder<>();
+          RexNode downDefinition = builder.lessThan(
+              builder.call(SqlStdOperatorTable.PREV,
+                  builder.patternField("DOWN", intType, 3),
+                  builder.literal(0)),
+              builder.call(SqlStdOperatorTable.PREV,
+                  builder.patternField("DOWN", intType, 3),
+                  builder.literal(1)));
+          pdBuilder.put("DOWN", downDefinition);
+          RexNode upDefinition = builder.greaterThan(
+              builder.call(SqlStdOperatorTable.PREV,
+                  builder.patternField("UP", intType, 3),
+                  builder.literal(0)),
+              builder.call(SqlStdOperatorTable.PREV,
+                  builder.patternField("UP", intType, 3),
+                  builder.literal(1)));
+          pdBuilder.put("UP", upDefinition);
+
+          ImmutableList.Builder<RexNode> measuresBuilder = new 
ImmutableList.Builder<>();
+          measuresBuilder.add(
+              builder.alias(builder.patternField("STRT", intType, 3), 
"start_nw"));
+          measuresBuilder.add(
+              builder.alias(
+                  builder.call(SqlStdOperatorTable.LAST,
+                          builder.patternField("DOWN", intType, 3),
+                          builder.literal(0)),
+                  "bottom_nw"));
+
+          RexNode after = builder.getRexBuilder().makeFlag(
+              SqlMatchRecognize.AfterOption.SKIP_TO_NEXT_ROW);
+
+          ImmutableList.Builder<RexNode> partitionKeysBuilder = new 
ImmutableList.Builder<>();
+          partitionKeysBuilder.add(builder.field("DEPTNO"));
+
+          ImmutableList.Builder<RexNode> orderKeysBuilder = new 
ImmutableList.Builder<>();
+          orderKeysBuilder.add(builder.field("EMPNO"));
+
+          RexNode interval = builder.literal("INTERVAL '5' SECOND");
+
           builder
-              .scan("EMP")
-              .filter(
-                  builder.equals(
-                      builder.field("EMPNO"),
-                      builder.literal(124)))
+              .match(pattern, false, false, pdBuilder.build(),
+                  measuresBuilder.build(), after, ImmutableMap.of(), false,
+                  partitionKeysBuilder.build(), orderKeysBuilder.build(), 
interval)
               .hints(indexHint);
         },
         "hints() should fail on non Hintable relational expression");
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 07b3356922..0f3154a0ef 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
@@ -40,6 +40,7 @@ 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.core.Window;
 import org.apache.calcite.rel.hint.HintPredicate;
 import org.apache.calcite.rel.hint.HintPredicates;
 import org.apache.calcite.rel.hint.HintStrategy;
@@ -48,8 +49,14 @@ 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.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalIntersect;
 import org.apache.calcite.rel.logical.LogicalJoin;
+import org.apache.calcite.rel.logical.LogicalMinus;
 import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.logical.LogicalSort;
+import org.apache.calcite.rel.logical.LogicalUnion;
+import org.apache.calcite.rel.logical.LogicalValues;
 import org.apache.calcite.rel.rules.CoreRules;
 import org.apache.calcite.sql.SqlDelete;
 import org.apache.calcite.sql.SqlInsert;
@@ -187,6 +194,54 @@ class SqlHintsConverterTest {
     sql(sql).ok();
   }
 
+  @Test void testFilterHints() {
+    final String sql = "select /*+ resource(parallelism='3') */ avg(sal) as 
avg_sal, deptno\n"
+            + "from emp group by deptno having avg(sal) > 5000";
+    sql(sql).ok();
+  }
+
+  @Test void testUnionHints() {
+    final String sql = "select /*+ breakable */ deptno from\n"
+            + "(select ename, deptno from emp\n"
+            + "union all\n"
+            + "select name, deptno from dept)";
+    sql(sql).ok();
+  }
+
+  @Test void testMinusHints() {
+    final String sql = "select /*+ breakable */ deptno from\n"
+        + "(select ename, deptno from emp\n"
+        + "except all\n"
+        + "select name, deptno from dept)";
+    sql(sql).ok();
+  }
+
+  @Test void testIntersectHints() {
+    final String sql = "select /*+ breakable */ deptno from\n"
+        + "(select ename, deptno from emp\n"
+        + "intersect all\n"
+        + "select name, deptno from dept)";
+    sql(sql).ok();
+  }
+
+  @Test void testSortHints() {
+    final String sql = "select /*+ async_merge */ empno from emp order by 
empno, empno desc";
+    sql(sql).ok();
+  }
+
+  @Test void testValuesHints() {
+    final String sql = "select /*+ resource(parallelism='3') */ a, max(b), 
max(b + 1)\n"
+        + "from (values (1, 2)) as t(a, b)\n"
+        + "group by a";
+    sql(sql).ok();
+  }
+
+  @Test void testWindowHints() {
+    final String sql = "select /*+ mini_batch */ last_value(deptno)\n"
+        + "over (order by empno rows 2 following) from emp";
+    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"
@@ -711,38 +766,90 @@ class SqlHintsConverterTest {
 
       @Override public RelNode visit(TableScan scan) {
         if (scan.getHints().size() > 0) {
-          this.hintsCollect.add("TableScan:" + scan.getHints().toString());
+          this.hintsCollect.add("TableScan:" + scan.getHints());
         }
         return super.visit(scan);
       }
 
       @Override public RelNode visit(LogicalJoin join) {
         if (join.getHints().size() > 0) {
-          this.hintsCollect.add("LogicalJoin:" + join.getHints().toString());
+          this.hintsCollect.add("LogicalJoin:" + join.getHints());
         }
         return super.visit(join);
       }
 
       @Override public RelNode visit(LogicalProject project) {
         if (project.getHints().size() > 0) {
-          this.hintsCollect.add("Project:" + project.getHints().toString());
+          this.hintsCollect.add("Project:" + project.getHints());
         }
         return super.visit(project);
       }
 
       @Override public RelNode visit(LogicalAggregate aggregate) {
         if (aggregate.getHints().size() > 0) {
-          this.hintsCollect.add("Aggregate:" + 
aggregate.getHints().toString());
+          this.hintsCollect.add("Aggregate:" + aggregate.getHints());
         }
         return super.visit(aggregate);
       }
 
       @Override public RelNode visit(LogicalCorrelate correlate) {
         if (correlate.getHints().size() > 0) {
-          this.hintsCollect.add("Correlate:" + 
correlate.getHints().toString());
+          this.hintsCollect.add("Correlate:" + correlate.getHints());
         }
         return super.visit(correlate);
       }
+
+      @Override public RelNode visit(LogicalFilter filter) {
+        if (filter.getHints().size() > 0) {
+          this.hintsCollect.add("Filter:" + filter.getHints());
+        }
+        return super.visit(filter);
+      }
+
+      @Override public RelNode visit(LogicalUnion union) {
+        if (union.getHints().size() > 0) {
+          this.hintsCollect.add("Union:" + union.getHints());
+        }
+        return super.visit(union);
+      }
+
+      @Override public RelNode visit(LogicalIntersect intersect) {
+        if (intersect.getHints().size() > 0) {
+          this.hintsCollect.add("Intersect:" + intersect.getHints());
+        }
+        return super.visit(intersect);
+      }
+
+      @Override public RelNode visit(LogicalMinus minus) {
+        if (minus.getHints().size() > 0) {
+          this.hintsCollect.add("Minus:" + minus.getHints());
+        }
+        return super.visit(minus);
+      }
+
+      @Override public RelNode visit(LogicalSort sort) {
+        if (sort.getHints().size() > 0) {
+          this.hintsCollect.add("Sort:" + sort.getHints());
+        }
+        return super.visit(sort);
+      }
+
+      @Override public RelNode visit(LogicalValues values) {
+        if (values.getHints().size() > 0) {
+          this.hintsCollect.add("Values:" + values.getHints());
+        }
+        return super.visit(values);
+      }
+
+      @Override public RelNode visit(RelNode other) {
+        if (other instanceof Window) {
+          Window window = (Window) other;
+          if (window.getHints().size() > 0) {
+            this.hintsCollect.add("Window:" + window.getHints());
+          }
+        }
+        return super.visit(other);
+      }
     }
   }
 
@@ -809,7 +916,8 @@ class SqlHintsConverterTest {
         .hintStrategy("properties", HintPredicates.TABLE_SCAN)
         .hintStrategy(
             "resource", HintPredicates.or(
-            HintPredicates.PROJECT, HintPredicates.AGGREGATE, 
HintPredicates.CALC))
+            HintPredicates.PROJECT, HintPredicates.AGGREGATE,
+                HintPredicates.CALC, HintPredicates.VALUES, 
HintPredicates.FILTER))
         .hintStrategy("AGG_STRATEGY",
             HintStrategy.builder(HintPredicates.AGGREGATE)
                 .optionChecker(
@@ -824,6 +932,10 @@ class SqlHintsConverterTest {
           HintPredicates.or(
               HintPredicates.and(HintPredicates.CORRELATE, 
temporalJoinWithFixedTableName()),
               HintPredicates.and(HintPredicates.JOIN, 
joinWithFixedTableName())))
+        .hintStrategy("breakable", HintPredicates.SETOP)
+        .hintStrategy("async_merge", HintPredicates.SORT)
+        .hintStrategy("mini_batch",
+                HintPredicates.and(HintPredicates.WINDOW, 
HintPredicates.PROJECT))
         .hintStrategy("use_merge_join",
             HintStrategy.builder(
                 HintPredicates.and(HintPredicates.JOIN, 
joinWithFixedTableName()))
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 3b4422a65b..052a6b72a0 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
@@ -46,7 +46,6 @@ 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 *
@@ -56,6 +55,20 @@ from orders, products_temporal for system_time as of 
orders.rowtime]]>
       <![CDATA[
 Project:[[USE_HASH_JOIN inheritPath:[] options:[ORDERS, PRODUCTS_TEMPORAL]]]
 Correlate:[[USE_HASH_JOIN inheritPath:[0] options:[ORDERS, PRODUCTS_TEMPORAL]]]
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testFilterHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ resource(parallelism='3') */ avg(sal) as avg_sal, 
deptno
+from emp group by deptno having avg(sal) > 5000]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[RESOURCE inheritPath:[] options:{PARALLELISM=3}]]
+Filter:[[RESOURCE inheritPath:[0] options:{PARALLELISM=3}]]
+Aggregate:[[RESOURCE inheritPath:[0, 0] options:{PARALLELISM=3}]]
+Project:[[RESOURCE inheritPath:[0, 0, 0] options:{PARALLELISM=3}]]
 ]]>
     </Resource>
   </TestCase>
@@ -155,8 +168,10 @@ select /*+ resource(cpu='2') */ avg(e2.sal) from emp e2 
where e2.deptno = d1.dep
       <![CDATA[
 Aggregate:[[RESOURCE inheritPath:[] options:{PARALLELISM=3}]]
 Project:[[RESOURCE inheritPath:[0] options:{PARALLELISM=3}]]
+Filter:[[RESOURCE inheritPath:[0, 0] options:{PARALLELISM=3}]]
 Aggregate:[[RESOURCE inheritPath:[] options:{CPU=2}], [RESOURCE 
inheritPath:[0, 0, 0, 1] options:{PARALLELISM=3}]]
 Project:[[RESOURCE inheritPath:[0] options:{CPU=2}], [RESOURCE inheritPath:[0, 
0, 0, 1, 0] options:{PARALLELISM=3}]]
+Filter:[[RESOURCE inheritPath:[0, 0] options:{CPU=2}], [RESOURCE 
inheritPath:[0, 0, 0, 1, 0, 0] options:{PARALLELISM=3}]]
 ]]>
     </Resource>
   </TestCase>
@@ -180,6 +195,20 @@ EnumerableProject(ENAME=[$3], JOB=[$4], SAL=[$7], 
NAME=[$1])
   EnumerableHashJoin(condition=[=($0, $9)], joinType=[inner])
     EnumerableTableScan(table=[[CATALOG, SALES, DEPT]])
     EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testIntersectHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ breakable */ deptno from
+(select ename, deptno from emp
+intersect all
+select name, deptno from dept)]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[BREAKABLE inheritPath:[]]]
+Intersect:[[BREAKABLE inheritPath:[0]]]
 ]]>
     </Resource>
   </TestCase>
@@ -193,6 +222,20 @@ from emp join dept on emp.deptno = dept.deptno]]>
       <![CDATA[
 Project:[[USE_HASH_JOIN inheritPath:[] options:[R, S]], [USE_HASH_JOIN 
inheritPath:[] options:[EMP, DEPT]]]
 LogicalJoin:[[USE_HASH_JOIN inheritPath:[0] options:[EMP, DEPT]]]
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testMinusHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ breakable */ deptno from
+(select ename, deptno from emp
+except all
+select name, deptno from dept)]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[BREAKABLE inheritPath:[]]]
+Minus:[[BREAKABLE inheritPath:[0]]]
 ]]>
     </Resource>
   </TestCase>
@@ -249,6 +292,16 @@ on emp.deptno = dept.deptno]]>
 Project:[[PROPERTIES inheritPath:[] options:{K1=v1, K2=v2}]]
 TableScan:[[INDEX inheritPath:[] options:[IDX1, IDX2]], [PROPERTIES 
inheritPath:[0, 0] options:{K1=v1, K2=v2}]]
 TableScan:[[PROPERTIES inheritPath:[] options:{K1=v1, K2=v2}], [PROPERTIES 
inheritPath:[0, 1] options:{K1=v1, K2=v2}]]
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testSortHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ async_merge */ empno from emp order by empno, empno 
desc]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Sort:[[ASYNC_MERGE inheritPath:[]]]
 ]]>
     </Resource>
   </TestCase>
@@ -304,6 +357,20 @@ from emp left join dept on emp.deptno = dept.deptno)]]>
       <![CDATA[
 Project:[[RESOURCE inheritPath:[] options:{MEM=20Mb}], [RESOURCE 
inheritPath:[] options:{PARALLELISM=3}], [NO_HASH_JOIN inheritPath:[]]]
 LogicalJoin:[[NO_HASH_JOIN inheritPath:[0]]]
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testUnionHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ breakable */ deptno from
+(select ename, deptno from emp
+union all
+select name, deptno from dept)]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[BREAKABLE inheritPath:[]]]
+Union:[[BREAKABLE inheritPath:[0]]]
 ]]>
     </Resource>
   </TestCase>
@@ -329,6 +396,31 @@ LogicalProject(ENAME=[$1], JOB=[$2], SAL=[$5], NAME=[$10])
   LogicalJoin(condition=[=($7, $9)], joinType=[inner])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
     LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testValuesHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ resource(parallelism='3') */ a, max(b), max(b + 1)
+from (values (1, 2)) as t(a, b)
+group by a]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Aggregate:[[RESOURCE inheritPath:[] options:{PARALLELISM=3}]]
+Project:[[RESOURCE inheritPath:[0] options:{PARALLELISM=3}]]
+Values:[[RESOURCE inheritPath:[0, 0] options:{PARALLELISM=3}]]
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testWindowHints">
+    <Resource name="sql">
+      <![CDATA[select /*+ mini_batch */ last_value(deptno)
+over (order by empno rows 2 following) from emp]]>
+    </Resource>
+    <Resource name="hints">
+      <![CDATA[
+Project:[[MINI_BATCH inheritPath:[]]]
 ]]>
     </Resource>
   </TestCase>

Reply via email to