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 6257609  [CALCITE-3730] Add hints to RelBuilder
6257609 is described below

commit 6257609c6be438ba9103666ea41e3191d851abd0
Author: yuzhao.cyz <yuzhao....@gmail.com>
AuthorDate: Tue Jan 14 11:52:33 2020 +0800

    [CALCITE-3730] Add hints to RelBuilder
    
    * Add #hints to RelBuilder
    * Add hints to RelNode factories
    * In logical RelNode classes that implement Hintable, add hints to #create 
method
---
 .../org/apache/calcite/adapter/jdbc/JdbcRules.java |   8 +-
 .../apache/calcite/plan/RelOptAbstractTable.java   |   3 +-
 .../apache/calcite/plan/RelOptMaterialization.java |   4 +-
 .../java/org/apache/calcite/plan/RelOptUtil.java   |  17 +--
 .../org/apache/calcite/plan/ViewExpanders.java     |   2 +-
 .../apache/calcite/prepare/LixToRelTranslator.java |   7 +-
 .../calcite/prepare/QueryableRelBuilder.java       |   6 +-
 .../apache/calcite/prepare/RelOptTableImpl.java    |   6 +-
 .../main/java/org/apache/calcite/rel/RelRoot.java  |   6 +-
 .../org/apache/calcite/rel/core/RelFactories.java  |  79 ++++++++++----
 .../calcite/rel/logical/LogicalAggregate.java      |  19 +++-
 .../apache/calcite/rel/logical/LogicalJoin.java    |  33 ++++--
 .../apache/calcite/rel/logical/LogicalProject.java |  20 +++-
 .../calcite/rel/logical/LogicalTableScan.java      |   9 +-
 .../calcite/rel/logical/ToLogicalConverter.java    |   2 +-
 .../rel/rules/JoinAddRedundantSemiJoinRule.java    |   2 +
 .../calcite/rel/rules/LoptSemiJoinOptimizer.java   |   4 +-
 .../rel/rules/SemiJoinFilterTransposeRule.java     |   3 +
 .../rel/rules/SemiJoinJoinTransposeRule.java       |   3 +
 .../rel/rules/SemiJoinProjectTransposeRule.java    |   7 +-
 .../org/apache/calcite/rel/stream/StreamRules.java |   8 +-
 .../main/java/org/apache/calcite/sql/SqlUtil.java  |   6 +-
 .../apache/calcite/sql2rel/RelDecorrelator.java    |  11 +-
 .../sql2rel/RelStructuredTypeFlattener.java        |   8 +-
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  40 ++++---
 .../java/org/apache/calcite/tools/RelBuilder.java  | 117 ++++++++++++++++++---
 .../org/apache/calcite/plan/RelOptUtilTest.java    |   2 +
 .../org/apache/calcite/plan/RelWriterTest.java     |  12 ++-
 .../calcite/plan/volcano/TraitPropagationTest.java |   3 +-
 .../java/org/apache/calcite/test/Matchers.java     |  12 +++
 .../org/apache/calcite/test/RelBuilderTest.java    |  89 ++++++++++++++++
 .../org/apache/calcite/test/RelMetadataTest.java   |  35 +++---
 .../apache/calcite/test/SqlHintsConverterTest.java |  11 +-
 .../org/apache/calcite/test/SqlToRelTestBase.java  |   6 +-
 .../calcite/test/catalog/MockCatalogReader.java    |  11 +-
 .../apache/calcite/test/SqlHintsConverterTest.xml  |   4 +-
 .../apache/calcite/adapter/druid/DruidTable.java   |   2 +-
 .../calcite/adapter/pig/PigRelFactories.java       |  16 ++-
 .../org/apache/calcite/piglet/PigRelBuilder.java   |   4 +-
 site/_docs/history.md                              |   1 +
 .../calcite/adapter/splunk/SplunkPushDownRule.java |   5 +-
 41 files changed, 480 insertions(+), 163 deletions(-)

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 52525c0..c8a3e64 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
@@ -96,7 +96,7 @@ public class JdbcRules {
   protected static final Logger LOGGER = CalciteTrace.getPlannerTracer();
 
   static final RelFactories.ProjectFactory PROJECT_FACTORY =
-      (input, projects, fieldNames) -> {
+      (input, hints, projects, fieldNames) -> {
         final RelOptCluster cluster = input.getCluster();
         final RelDataType rowType =
             RexUtil.createStructType(cluster.getTypeFactory(), projects,
@@ -114,7 +114,7 @@ public class JdbcRules {
       };
 
   static final RelFactories.JoinFactory JOIN_FACTORY =
-      (left, right, condition, variablesSet, joinType, semiJoinDone) -> {
+      (left, right, hints, condition, variablesSet, joinType, semiJoinDone) -> 
{
         final RelOptCluster cluster = left.getCluster();
         final RelTraitSet traitSet = cluster.traitSetOf(left.getConvention());
         try {
@@ -146,7 +146,7 @@ public class JdbcRules {
       };
 
   public static final RelFactories.AggregateFactory AGGREGATE_FACTORY =
-      (input, groupSet, groupSets, aggCalls) -> {
+      (input, hints, groupSet, groupSets, aggCalls) -> {
         final RelOptCluster cluster = input.getCluster();
         final RelTraitSet traitSet = cluster.traitSetOf(input.getConvention());
         try {
@@ -187,7 +187,7 @@ public class JdbcRules {
       };
 
   public static final RelFactories.TableScanFactory TABLE_SCAN_FACTORY =
-      (cluster, table) -> {
+      (cluster, table, hints) -> {
         throw new UnsupportedOperationException();
       };
 
diff --git 
a/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
index 05333a3..216f7fb 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptAbstractTable.java
@@ -108,7 +108,8 @@ public abstract class RelOptAbstractTable implements 
RelOptTable {
   }
 
   public RelNode toRel(ToRelContext context) {
-    return LogicalTableScan.create(context.getCluster(), this);
+    return LogicalTableScan.create(context.getCluster(), this,
+        context.getTableHints());
   }
 
   public Expression getExpression(Class clazz) {
diff --git 
a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
index 1baf55f..0b9a48a 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
@@ -156,7 +156,7 @@ public class RelOptMaterialization {
                           Mappings.offsetSource(rightMapping, offset),
                           leftMapping.getTargetCount()));
               final RelNode project = RelOptUtil.createProject(
-                  LogicalTableScan.create(cluster, leftRelOptTable),
+                  LogicalTableScan.create(cluster, leftRelOptTable, 
ImmutableList.of()),
                   Mappings.asList(mapping.inverse()));
               final List<RexNode> conditions = new ArrayList<>();
               if (left.condition != null) {
@@ -180,7 +180,7 @@ public class RelOptMaterialization {
                       Mappings.offsetSource(leftMapping, offset),
                       Mappings.offsetTarget(rightMapping, leftCount));
               final RelNode project = RelOptUtil.createProject(
-                  LogicalTableScan.create(cluster, rightRelOptTable),
+                  LogicalTableScan.create(cluster, rightRelOptTable, 
ImmutableList.of()),
                   Mappings.asList(mapping.inverse()));
               final List<RexNode> conditions = new ArrayList<>();
               if (left.condition != null) {
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index a965a23..a69afcd 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -667,8 +667,8 @@ public abstract class RelOptUtil {
         || logic == RelOptUtil.Logic.TRUE_FALSE_UNKNOWN;
     if (!outerJoin) {
       final LogicalAggregate aggregate =
-          LogicalAggregate.create(ret, ImmutableBitSet.range(keyCount), null,
-              ImmutableList.of());
+          LogicalAggregate.create(ret, ImmutableList.of(), 
ImmutableBitSet.range(keyCount),
+              null, ImmutableList.of());
       return new Exists(aggregate, false, false);
     }
 
@@ -867,14 +867,17 @@ public abstract class RelOptUtil {
     final RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
     List<RexNode> castExps;
     RelNode input;
+    List<RelHint> hints = ImmutableList.of();
     if (rel instanceof Project) {
       // No need to create another project node if the rel
       // is already a project.
+      final Project project = (Project) rel;
       castExps = RexUtil.generateCastExpressions(
           rexBuilder,
           castRowType,
           ((Project) rel).getProjects());
       input = rel.getInput(0);
+      hints = project.getHints();
     } else {
       castExps = RexUtil.generateCastExpressions(
           rexBuilder,
@@ -884,11 +887,11 @@ public abstract class RelOptUtil {
     }
     if (rename) {
       // Use names and types from castRowType.
-      return projectFactory.createProject(input, castExps,
+      return projectFactory.createProject(input, hints, castExps,
           castRowType.getFieldNames());
     } else {
       // Use names from rowType, types from castRowType.
-      return projectFactory.createProject(input, castExps,
+      return projectFactory.createProject(input, hints, castExps,
           rowType.getFieldNames());
     }
   }
@@ -927,13 +930,15 @@ public abstract class RelOptUtil {
               null));
     }
 
-    return LogicalAggregate.create(rel, ImmutableBitSet.of(), null, aggCalls);
+    return LogicalAggregate.create(rel, ImmutableList.of(), 
ImmutableBitSet.of(),
+        null, aggCalls);
   }
 
   /** @deprecated Use {@link RelBuilder#distinct()}. */
   @Deprecated // to be removed before 2.0
   public static RelNode createDistinctRel(RelNode rel) {
     return LogicalAggregate.create(rel,
+        ImmutableList.of(),
         ImmutableBitSet.range(rel.getRowType().getFieldCount()), null,
         ImmutableList.of());
   }
@@ -3381,7 +3386,7 @@ public abstract class RelOptUtil {
               : fieldNames.get(i));
       exprList.add(rexBuilder.makeInputRef(rel, source));
     }
-    return projectFactory.createProject(rel, exprList, outputNameList);
+    return projectFactory.createProject(rel, ImmutableList.of(), exprList, 
outputNameList);
   }
 
   /** Predicate for whether a {@link Calc} contains multisets or windowed
diff --git a/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java 
b/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java
index 13c188e..0767307 100644
--- a/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java
+++ b/core/src/main/java/org/apache/calcite/plan/ViewExpanders.java
@@ -80,7 +80,7 @@ public abstract class ViewExpanders {
       }
 
       public List<RelHint> getTableHints() {
-        throw new UnsupportedOperationException();
+        return ImmutableList.of();
       }
     };
   }
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java 
b/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
index e6ab3bc..6bdb9be 100644
--- a/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
+++ b/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
@@ -88,6 +88,7 @@ class LixToRelTranslator {
       case SELECT:
         input = translate(call.targetExpression);
         return LogicalProject.create(input,
+            ImmutableList.of(),
             toRex(input, (FunctionExpression) call.expressions.get(0)),
             (List<String>) null);
 
@@ -103,7 +104,8 @@ class LixToRelTranslator {
                     Types.toClass(
                         
Types.getElementType(call.targetExpression.getType()))),
                 ImmutableList.of(),
-                call.targetExpression));
+                call.targetExpression),
+            ImmutableList.of());
 
       case SCHEMA_GET_TABLE:
         return LogicalTableScan.create(cluster,
@@ -111,7 +113,8 @@ class LixToRelTranslator {
                 typeFactory.createJavaType((Class)
                     ((ConstantExpression) call.expressions.get(1)).value),
                 ImmutableList.of(),
-                call.targetExpression));
+                call.targetExpression),
+            ImmutableList.of());
 
       default:
         throw new UnsupportedOperationException(
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java 
b/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
index b43c0b6..9624470 100644
--- a/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
@@ -48,6 +48,8 @@ import org.apache.calcite.schema.QueryableTable;
 import org.apache.calcite.schema.TranslatableTable;
 import org.apache.calcite.schema.impl.AbstractTableQueryable;
 
+import com.google.common.collect.ImmutableList;
+
 import java.math.BigDecimal;
 import java.util.Comparator;
 import java.util.List;
@@ -102,7 +104,7 @@ class QueryableRelBuilder<T> implements QueryableFactory<T> 
{
         return ((TranslatableTable) table).toRel(translator.toRelContext(),
             relOptTable);
       } else {
-        return LogicalTableScan.create(translator.cluster, relOptTable);
+        return LogicalTableScan.create(translator.cluster, relOptTable, 
ImmutableList.of());
       }
     }
     return translator.translate(queryable.getExpression());
@@ -536,7 +538,7 @@ class QueryableRelBuilder<T> implements QueryableFactory<T> 
{
     RelNode child = toRel(source);
     List<RexNode> nodes = translator.toRexList(selector, child);
     setRel(
-        LogicalProject.create(child, nodes, (List<String>)  null));
+        LogicalProject.create(child, ImmutableList.of(), nodes, (List<String>) 
 null));
     return null;
   }
 
diff --git a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java 
b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
index 769dd27..021a300 100644
--- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
@@ -290,7 +290,7 @@ public class RelOptTableImpl extends 
Prepare.AbstractPreparingTable {
     }
     final RelOptCluster cluster = context.getCluster();
     if (Hook.ENABLE_BINDABLE.get(false)) {
-      return LogicalTableScan.create(cluster, this);
+      return LogicalTableScan.create(cluster, this, context.getTableHints());
     }
     if (CalciteSystemProperty.ENABLE_ENUMERABLE.value()
         && table instanceof QueryableTable
@@ -301,7 +301,7 @@ public class RelOptTableImpl extends 
Prepare.AbstractPreparingTable {
     if (table instanceof ScannableTable
         || table instanceof FilterableTable
         || table instanceof ProjectableFilterableTable) {
-      return LogicalTableScan.create(cluster, this);
+      return LogicalTableScan.create(cluster, this, context.getTableHints());
     }
     // Some tests rely on the old behavior when tables were immediately 
converted to
     // EnumerableTableScan
@@ -311,7 +311,7 @@ public class RelOptTableImpl extends 
Prepare.AbstractPreparingTable {
         || EnumerableTableScan.canHandle(this))) {
       return EnumerableTableScan.create(cluster, this);
     }
-    return LogicalTableScan.create(cluster, this);
+    return LogicalTableScan.create(cluster, this, context.getTableHints());
   }
 
   public List<RelCollation> getCollationList() {
diff --git a/core/src/main/java/org/apache/calcite/rel/RelRoot.java 
b/core/src/main/java/org/apache/calcite/rel/RelRoot.java
index 80cd6ce..27e8e45 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelRoot.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelRoot.java
@@ -16,7 +16,6 @@
  */
 package org.apache.calcite.rel;
 
-import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.type.RelDataType;
@@ -167,10 +166,7 @@ public class RelRoot {
     for (Pair<Integer, String> field : fields) {
       projects.add(rexBuilder.makeInputRef(rel, field.left));
     }
-    return RelOptUtil.copyRelHints(
-        rel,
-        LogicalProject.create(rel, projects, Pair.right(fields)),
-        false);
+    return LogicalProject.create(rel, hints, projects, Pair.right(fields));
   }
 
   public boolean isNameTrivial() {
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 92473bc..4518524 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
@@ -25,6 +25,7 @@ import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelDistribution;
 import org.apache.calcite.rel.RelNode;
+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.LogicalExchange;
@@ -143,9 +144,23 @@ public class RelFactories {
    * appropriate type for this rule's calling convention.
    */
   public interface ProjectFactory {
-    /** Creates a project. */
-    RelNode createProject(RelNode input, List<? extends RexNode> childExprs,
-        List<String> fieldNames);
+    /**
+     * Creates a project.
+     *
+     * @param input The input
+     * @param hints The hints
+     * @param childExprs The projection expressions
+     * @param fieldNames The projection field names
+     * @return a project
+     */
+    RelNode createProject(RelNode input, List<RelHint> hints,
+        List<? extends RexNode> childExprs, List<String> fieldNames);
+
+    @Deprecated // to be removed before 1.23
+    default RelNode createProject(RelNode input,
+        List<? extends RexNode> childExprs, List<String> fieldNames) {
+      return createProject(input, ImmutableList.of(), childExprs, fieldNames);
+    }
   }
 
   /**
@@ -153,9 +168,9 @@ public class RelFactories {
    * {@link org.apache.calcite.rel.logical.LogicalProject}.
    */
   private static class ProjectFactoryImpl implements ProjectFactory {
-    public RelNode createProject(RelNode input,
+    public RelNode createProject(RelNode input, List<RelHint> hints,
         List<? extends RexNode> childExprs, List<String> fieldNames) {
-      return LogicalProject.create(input, childExprs, fieldNames);
+      return LogicalProject.create(input, hints, childExprs, fieldNames);
     }
   }
 
@@ -272,15 +287,21 @@ public class RelFactories {
    */
   public interface AggregateFactory {
     /** Creates an aggregate. */
-    RelNode createAggregate(RelNode input, ImmutableBitSet groupSet,
+    RelNode createAggregate(RelNode input, List<RelHint> hints, 
ImmutableBitSet groupSet,
         ImmutableList<ImmutableBitSet> groupSets, List<AggregateCall> 
aggCalls);
 
-    @Deprecated // to be removed before 2.0
+    @Deprecated // to be removed before 1.23
+    default RelNode createAggregate(RelNode input, ImmutableBitSet groupSet,
+        ImmutableList<ImmutableBitSet> groupSets, List<AggregateCall> 
aggCalls) {
+      return createAggregate(input, ImmutableList.of(), groupSet, groupSets, 
aggCalls);
+    }
+
+    @Deprecated // to be removed before 1.23
     default RelNode createAggregate(RelNode input, boolean indicator,
         ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
         List<AggregateCall> aggCalls) {
       Aggregate.checkIndicator(indicator);
-      return createAggregate(input, groupSet, groupSets, aggCalls);
+      return createAggregate(input, ImmutableList.of(), groupSet, groupSets, 
aggCalls);
     }
   }
 
@@ -289,10 +310,10 @@ public class RelFactories {
    * that returns a vanilla {@link LogicalAggregate}.
    */
   private static class AggregateFactoryImpl implements AggregateFactory {
-    public RelNode createAggregate(RelNode input,
+    public RelNode createAggregate(RelNode input, List<RelHint> hints,
         ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
         List<AggregateCall> aggCalls) {
-      return LogicalAggregate.create(input, groupSet, groupSets, aggCalls);
+      return LogicalAggregate.create(input, hints, groupSet, groupSets, 
aggCalls);
     }
   }
 
@@ -346,6 +367,7 @@ public class RelFactories {
      *
      * @param left             Left input
      * @param right            Right input
+     * @param hints            Hints
      * @param condition        Join condition
      * @param variablesSet     Set of variables that are set by the
      *                         LHS and used by the RHS and are not available to
@@ -354,15 +376,23 @@ public class RelFactories {
      * @param semiJoinDone     Whether this join has been translated to a
      *                         semi-join
      */
-    RelNode createJoin(RelNode left, RelNode right, RexNode condition,
-        Set<CorrelationId> variablesSet, JoinRelType joinType,
+    RelNode createJoin(RelNode left, RelNode right, List<RelHint> hints,
+        RexNode condition, Set<CorrelationId> variablesSet, JoinRelType 
joinType,
         boolean semiJoinDone);
 
-    @Deprecated // to be removed before 2.0
+    @Deprecated // to be removed before 1.23
+    default RelNode createJoin(RelNode left, RelNode right, RexNode condition,
+        Set<CorrelationId> variablesSet, JoinRelType joinType,
+        boolean semiJoinDone) {
+      return createJoin(left, right, ImmutableList.of(), condition, 
variablesSet,
+          joinType, semiJoinDone);
+    }
+
+    @Deprecated // to be removed before 1.23
     default RelNode createJoin(RelNode left, RelNode right, RexNode condition,
         JoinRelType joinType, Set<String> variablesStopped,
         boolean semiJoinDone) {
-      return createJoin(left, right, condition,
+      return createJoin(left, right, ImmutableList.of(), condition,
           CorrelationId.setOf(variablesStopped), joinType, semiJoinDone);
     }
   }
@@ -372,10 +402,10 @@ public class RelFactories {
    * {@link org.apache.calcite.rel.logical.LogicalJoin}.
    */
   private static class JoinFactoryImpl implements JoinFactory {
-    public RelNode createJoin(RelNode left, RelNode right,
+    public RelNode createJoin(RelNode left, RelNode right, List<RelHint> hints,
         RexNode condition, Set<CorrelationId> variablesSet,
         JoinRelType joinType, boolean semiJoinDone) {
-      return LogicalJoin.create(left, right, condition, variablesSet, joinType,
+      return LogicalJoin.create(left, right, hints, condition, variablesSet, 
joinType,
           semiJoinDone, ImmutableList.of());
     }
   }
@@ -479,7 +509,12 @@ public class RelFactories {
     /**
      * Creates a {@link TableScan}.
      */
-    RelNode createScan(RelOptCluster cluster, RelOptTable table);
+    RelNode createScan(RelOptCluster cluster, RelOptTable table, List<RelHint> 
hints);
+
+    @Deprecated // to be removed before 1.23
+    default RelNode createScan(RelOptCluster cluster, RelOptTable table) {
+      return createScan(cluster, table, ImmutableList.of());
+    }
   }
 
   /**
@@ -487,8 +522,8 @@ public class RelFactories {
    * {@link LogicalTableScan}.
    */
   private static class TableScanFactoryImpl implements TableScanFactory {
-    public RelNode createScan(RelOptCluster cluster, RelOptTable table) {
-      return LogicalTableScan.create(cluster, table);
+    public RelNode createScan(RelOptCluster cluster, RelOptTable table, 
List<RelHint> hints) {
+      return LogicalTableScan.create(cluster, table, hints);
     }
   }
 
@@ -521,15 +556,15 @@ public class RelFactories {
   @Nonnull public static TableScanFactory expandingScanFactory(
       @Nonnull RelOptTable.ViewExpander viewExpander,
       @Nonnull TableScanFactory tableScanFactory) {
-    return (cluster, table) -> {
+    return (cluster, table, hints) -> {
       final TranslatableTable translatableTable =
           table.unwrap(TranslatableTable.class);
       if (translatableTable != null) {
         final RelOptTable.ToRelContext toRelContext =
-            ViewExpanders.toRelContext(viewExpander, cluster);
+            ViewExpanders.toRelContext(viewExpander, cluster, hints);
         return translatableTable.toRel(toRelContext, table);
       }
-      return tableScanFactory.createScan(cluster, table);
+      return tableScanFactory.createScan(cluster, table, hints);
     };
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java
index 3da516b..115e423 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java
@@ -107,9 +107,19 @@ public final class LogicalAggregate extends Aggregate {
 
   /** Creates a LogicalAggregate. */
   public static LogicalAggregate create(final RelNode input,
-      ImmutableBitSet groupSet, List<ImmutableBitSet> groupSets,
+      List<RelHint> hints,
+      ImmutableBitSet groupSet,
+      List<ImmutableBitSet> groupSets,
       List<AggregateCall> aggCalls) {
-    return create_(input, groupSet, groupSets, aggCalls);
+    return create_(input, hints, groupSet, groupSets, aggCalls);
+  }
+
+  @Deprecated // to be removed before 2.0
+  public static LogicalAggregate create(final RelNode input,
+      ImmutableBitSet groupSet,
+      List<ImmutableBitSet> groupSets,
+      List<AggregateCall> aggCalls) {
+    return create_(input, ImmutableList.of(), groupSet, groupSets, aggCalls);
   }
 
   @Deprecated // to be removed before 2.0
@@ -119,16 +129,17 @@ public final class LogicalAggregate extends Aggregate {
       List<ImmutableBitSet> groupSets,
       List<AggregateCall> aggCalls) {
     checkIndicator(indicator);
-    return create_(input, groupSet, groupSets, aggCalls);
+    return create_(input, ImmutableList.of(), groupSet, groupSets, aggCalls);
   }
 
   private static LogicalAggregate create_(final RelNode input,
+      List<RelHint> hints,
       ImmutableBitSet groupSet,
       List<ImmutableBitSet> groupSets,
       List<AggregateCall> aggCalls) {
     final RelOptCluster cluster = input.getCluster();
     final RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE);
-    return new LogicalAggregate(cluster, traitSet, ImmutableList.of(), input, 
groupSet,
+    return new LogicalAggregate(cluster, traitSet, hints, input, groupSet,
         groupSets, aggCalls);
   }
 
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java
index a3fb061..f37c3d7 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java
@@ -152,30 +152,45 @@ public final class LogicalJoin extends Join {
         ImmutableList.of());
   }
 
+  /** Creates a LogicalJoin. */
+  public static LogicalJoin create(RelNode left, RelNode right, List<RelHint> 
hints,
+      RexNode condition, Set<CorrelationId> variablesSet, JoinRelType 
joinType) {
+    return create(left, right, hints, condition, variablesSet, joinType, false,
+        ImmutableList.of());
+  }
+
   /** Creates a LogicalJoin, flagged with whether it has been translated to a
    * semi-join. */
-  public static LogicalJoin create(RelNode left, RelNode right,
+  public static LogicalJoin create(RelNode left, RelNode right, List<RelHint> 
hints,
       RexNode condition, Set<CorrelationId> variablesSet, JoinRelType joinType,
       boolean semiJoinDone, ImmutableList<RelDataTypeField> systemFieldList) {
     final RelOptCluster cluster = left.getCluster();
     final RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE);
-    return new LogicalJoin(cluster, traitSet, ImmutableList.of(), left, right, 
condition,
+    return new LogicalJoin(cluster, traitSet, hints, left, right, condition,
         variablesSet, joinType, semiJoinDone, systemFieldList);
   }
 
-  @Deprecated // to be removed before 2.0
+  @Deprecated // to be removed before 1.23
   public static LogicalJoin create(RelNode left, RelNode right,
-      RexNode condition, JoinRelType joinType, Set<String> variablesStopped,
+      RexNode condition, Set<CorrelationId> variablesSet, JoinRelType 
joinType) {
+    return create(left, right, ImmutableList.of(), condition, variablesSet,
+        joinType, false, ImmutableList.of());
+  }
+
+  @Deprecated // to be removed before 1.23
+  public static LogicalJoin create(RelNode left, RelNode right,
+      RexNode condition, Set<CorrelationId> variablesSet, JoinRelType joinType,
       boolean semiJoinDone, ImmutableList<RelDataTypeField> systemFieldList) {
-    return create(left, right, condition, 
CorrelationId.setOf(variablesStopped),
+    return create(left, right, ImmutableList.of(), condition, variablesSet,
         joinType, semiJoinDone, systemFieldList);
   }
 
-  /** Creates a LogicalJoin. */
+  @Deprecated // to be removed before 2.0
   public static LogicalJoin create(RelNode left, RelNode right,
-      RexNode condition, Set<CorrelationId> variablesSet, JoinRelType 
joinType) {
-    return create(left, right, condition, variablesSet, joinType, false,
-        ImmutableList.of());
+      RexNode condition, JoinRelType joinType, Set<String> variablesStopped,
+      boolean semiJoinDone, ImmutableList<RelDataTypeField> systemFieldList) {
+    return create(left, right, condition, 
CorrelationId.setOf(variablesStopped),
+        joinType, semiJoinDone, systemFieldList);
   }
 
   @Deprecated // to be removed before 2.0
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
index c5baecb..605b875 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
@@ -102,17 +102,17 @@ public final class LogicalProject extends Project {
   //~ Methods ----------------------------------------------------------------
 
   /** Creates a LogicalProject. */
-  public static LogicalProject create(final RelNode input,
+  public static LogicalProject create(final RelNode input, List<RelHint> hints,
       final List<? extends RexNode> projects, List<String> fieldNames) {
     final RelOptCluster cluster = input.getCluster();
     final RelDataType rowType =
         RexUtil.createStructType(cluster.getTypeFactory(), projects,
             fieldNames, SqlValidatorUtil.F_SUGGESTER);
-    return create(input, projects, rowType);
+    return create(input, hints, projects, rowType);
   }
 
   /** Creates a LogicalProject, specifying row type rather than field names. */
-  public static LogicalProject create(final RelNode input,
+  public static LogicalProject create(final RelNode input, List<RelHint> hints,
       final List<? extends RexNode> projects, RelDataType rowType) {
     final RelOptCluster cluster = input.getCluster();
     final RelMetadataQuery mq = cluster.getMetadataQuery();
@@ -120,7 +120,19 @@ public final class LogicalProject extends Project {
         cluster.traitSet().replace(Convention.NONE)
             .replaceIfs(RelCollationTraitDef.INSTANCE,
                 () -> RelMdCollation.project(mq, input, projects));
-    return new LogicalProject(cluster, traitSet, ImmutableList.of(), input, 
projects, rowType);
+    return new LogicalProject(cluster, traitSet, hints, input, projects, 
rowType);
+  }
+
+  @Deprecated // to be removed before 1.23
+  public static LogicalProject create(final RelNode input,
+      final List<? extends RexNode> projects, List<String> fieldNames) {
+    return create(input, ImmutableList.of(), projects, fieldNames);
+  }
+
+  @Deprecated // to be removed before 1.23
+  public static LogicalProject create(final RelNode input,
+      final List<? extends RexNode> projects, RelDataType rowType) {
+    return create(input, ImmutableList.of(), projects, rowType);
   }
 
   @Override public LogicalProject copy(RelTraitSet traitSet, RelNode input,
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableScan.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableScan.java
index 8876e01..9d62e37 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableScan.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableScan.java
@@ -29,7 +29,6 @@ import org.apache.calcite.schema.Table;
 
 import com.google.common.collect.ImmutableList;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -115,14 +114,10 @@ public final class LogicalTableScan extends TableScan {
     return new LogicalTableScan(cluster, traitSet, hints, relOptTable);
   }
 
-  /** Creates a LogicalTableScan.
-   *
-   * @param cluster Cluster
-   * @param relOptTable Table
-   */
+  @Deprecated // to be removed before 1.23
   public static LogicalTableScan create(RelOptCluster cluster,
       final RelOptTable relOptTable) {
-    return create(cluster, relOptTable, new ArrayList<>());
+    return create(cluster, relOptTable, ImmutableList.of());
   }
 
   @Override public RelNode withHints(List<RelHint> hintList) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java 
b/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
index 60ff322..5b262c2 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
@@ -52,7 +52,7 @@ public class ToLogicalConverter extends RelShuttleImpl {
   }
 
   @Override public RelNode visit(TableScan scan) {
-    return LogicalTableScan.create(scan.getCluster(), scan.getTable());
+    return LogicalTableScan.create(scan.getCluster(), scan.getTable(), 
scan.getHints());
   }
 
   @Override public RelNode visit(RelNode relNode) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
index de03c14..5cbd49d 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/JoinAddRedundantSemiJoinRule.java
@@ -26,6 +26,7 @@ import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.tools.RelBuilderFactory;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 /**
@@ -74,6 +75,7 @@ public class JoinAddRedundantSemiJoinRule extends RelOptRule {
     RelNode semiJoin =
         LogicalJoin.create(origJoinRel.getLeft(),
             origJoinRel.getRight(),
+            ImmutableList.of(),
             origJoinRel.getCondition(),
             ImmutableSet.of(),
             JoinRelType.SEMI);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java 
b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
index aed93a0..1ae11a5 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
@@ -37,6 +37,7 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Util;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
@@ -320,7 +321,7 @@ public class LoptSemiJoinOptimizer {
               multiJoin.getNumFieldsInJoinFactor(factIdx),
               semiJoinCondition);
     }
-    return LogicalJoin.create(factRel, dimRel, semiJoinCondition,
+    return LogicalJoin.create(factRel, dimRel, ImmutableList.of(), 
semiJoinCondition,
         ImmutableSet.of(), JoinRelType.SEMI);
   }
 
@@ -581,6 +582,7 @@ public class LoptSemiJoinOptimizer {
         LogicalJoin chosenSemiJoin =
             LogicalJoin.create(factRel,
                 chosenSemiJoins[bestDimIdx],
+                ImmutableList.of(),
                 semiJoin.getCondition(),
                 ImmutableSet.of(),
                 JoinRelType.SEMI);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
index b3068ba..1e15233 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinFilterTransposeRule.java
@@ -26,6 +26,7 @@ import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.tools.RelBuilderFactory;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 /**
@@ -65,6 +66,8 @@ public class SemiJoinFilterTransposeRule extends RelOptRule {
     RelNode newSemiJoin =
         LogicalJoin.create(filter.getInput(),
             semiJoin.getRight(),
+            // No need to copy the hints, the framework would try to do that.
+            ImmutableList.of(),
             semiJoin.getCondition(),
             ImmutableSet.of(),
             JoinRelType.SEMI);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
index cf83391..824d5e6 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinJoinTransposeRule.java
@@ -29,6 +29,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.tools.RelBuilderFactory;
 import org.apache.calcite.util.ImmutableIntList;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 import java.util.ArrayList;
@@ -159,6 +160,8 @@ public class SemiJoinJoinTransposeRule extends RelOptRule {
     LogicalJoin newSemiJoin =
         LogicalJoin.create(leftSemiJoinOp,
             semiJoin.getRight(),
+            // No need to copy the hints, the framework would try to do that.
+            ImmutableList.of(),
             newSemiJoinFilter,
             ImmutableSet.of(),
             JoinRelType.SEMI);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
index f601893..e786d8a 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
@@ -36,6 +36,7 @@ import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.tools.RelBuilderFactory;
 import org.apache.calcite.util.Pair;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 import java.util.List;
@@ -83,7 +84,11 @@ public class SemiJoinProjectTransposeRule extends RelOptRule 
{
     RexNode newCondition = adjustCondition(project, semiJoin);
 
     LogicalJoin newSemiJoin =
-        LogicalJoin.create(project.getInput(), semiJoin.getRight(), 
newCondition,
+        LogicalJoin.create(project.getInput(),
+            semiJoin.getRight(),
+            // No need to copy the hints, the framework would try to do that.
+            ImmutableList.of(),
+            newCondition,
             ImmutableSet.of(), JoinRelType.SEMI);
 
     // Create the new projection.  Note that the projection expressions
diff --git a/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java 
b/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
index 14c0c67..03ebb7b 100644
--- a/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
@@ -87,7 +87,9 @@ public class StreamRules {
       final Project project = call.rel(1);
       final LogicalDelta newDelta = LogicalDelta.create(project.getInput());
       final LogicalProject newProject =
-          LogicalProject.create(newDelta, project.getProjects(),
+          LogicalProject.create(newDelta,
+              project.getHints(),
+              project.getProjects(),
               project.getRowType().getFieldNames());
       call.transformTo(newProject);
     }
@@ -142,7 +144,7 @@ public class StreamRules {
       final LogicalDelta newDelta =
           LogicalDelta.create(aggregate.getInput());
       final LogicalAggregate newAggregate =
-          LogicalAggregate.create(newDelta, aggregate.getGroupSet(),
+          LogicalAggregate.create(newDelta, aggregate.getHints(), 
aggregate.getGroupSet(),
               aggregate.groupSets, aggregate.getAggCallList());
       call.transformTo(newAggregate);
     }
@@ -241,7 +243,7 @@ public class StreamRules {
                     .addAll(relOptTable.getQualifiedName())
                     .add("(STREAM)").build());
         final LogicalTableScan newScan =
-            LogicalTableScan.create(cluster, relOptTable2);
+            LogicalTableScan.create(cluster, relOptTable2, scan.getHints());
         call.transformTo(newScan);
       }
     }
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java 
b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
index 640b080..4f09cc9 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
@@ -988,10 +988,10 @@ public abstract class SqlUtil {
    * @return the {@code RelHint} list
    */
   public static List<RelHint> getRelHint(HintStrategyTable hintStrategies, 
SqlNodeList sqlHints) {
-    final List<RelHint> relHints = new ArrayList<>();
     if (sqlHints == null || sqlHints.size() == 0) {
-      return relHints;
+      return ImmutableList.of();
     }
+    final ImmutableList.Builder<RelHint> relHints = ImmutableList.builder();
     for (SqlNode node : sqlHints) {
       assert node instanceof SqlHint;
       final SqlHint sqlHint = (SqlHint) node;
@@ -1017,7 +1017,7 @@ public abstract class SqlUtil {
         relHints.add(relHint);
       }
     }
-    return ImmutableList.copyOf(relHints);
+    return relHints.build();
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index 0eae4d6..61b6422 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -1183,7 +1183,7 @@ public class RelDecorrelator implements ReflectiveVisitor 
{
     final RexNode condition =
         RexUtil.composeConjunction(relBuilder.getRexBuilder(), conditions);
     RelNode newJoin = relBuilder.push(leftFrame.r).push(rightFrame.r)
-        .join(rel.getJoinType(), condition, ImmutableSet.of()).build();
+        .join(rel.getJoinType(), condition).build();
 
     return register(rel, newJoin, mapOldToNewOutputs, corDefOutputs);
   }
@@ -1220,12 +1220,12 @@ public class RelDecorrelator implements 
ReflectiveVisitor {
     RelNode newJoin = relBuilder
         .push(leftFrame.r)
         .push(rightFrame.r)
-        .join(rel.getJoinType(), decorrelateExpr(currentRel, map, cm, 
rel.getCondition()),
+        .join(rel.getJoinType(),
+            decorrelateExpr(currentRel, map, cm, rel.getCondition()),
             ImmutableSet.of())
+        .hints(rel.getHints())
         .build();
 
-    newJoin = RelOptUtil.copyRelHints(rel, newJoin);
-
     // Create the mapping between the output of the old correlation rel
     // and the new join rel
     Map<Integer, Integer> mapOldToNewOutputs = new HashMap<>();
@@ -2299,8 +2299,7 @@ public class RelDecorrelator implements ReflectiveVisitor 
{
                       "nullIndicator")));
 
       Join join =
-          (Join) relBuilder.push(left).push(right)
-              .join(joinType, joinCond, ImmutableSet.of()).build();
+          (Join) relBuilder.push(left).push(right).join(joinType, 
joinCond).build();
 
       // To the consumer of joinOutputProjRel, nullIndicator is located
       // at the end
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 5436a26..4bf07b1 100644
--- 
a/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
+++ 
b/core/src/main/java/org/apache/calcite/sql2rel/RelStructuredTypeFlattener.java
@@ -434,12 +434,12 @@ public class RelStructuredTypeFlattener implements 
ReflectiveVisitor {
   }
 
   public void rewriteRel(LogicalJoin rel) {
-    LogicalJoin newRel =
+    final LogicalJoin newRel =
         LogicalJoin.create(getNewForOldRel(rel.getLeft()),
             getNewForOldRel(rel.getRight()),
+            rel.getHints(),
             rel.getCondition().accept(new RewriteRexShuttle()),
             rel.getVariablesSet(), rel.getJoinType());
-    newRel = (LogicalJoin) RelOptUtil.copyRelHints(rel, newRel);
     setNewForOldRel(rel, newRel);
   }
 
@@ -508,10 +508,10 @@ public class RelStructuredTypeFlattener implements 
ReflectiveVisitor {
     RelNode newInput = getNewForOldRel(rel.getInput());
     List<RexNode> newProjects = Pair.left(flattenedExpList);
     List<String> newNames = Pair.right(flattenedExpList);
-    RelNode newRel = relBuilder.push(newInput)
+    final RelNode newRel = relBuilder.push(newInput)
         .projectNamed(newProjects, newNames, true)
+        .hints(rel.getHints())
         .build();
-    newRel = RelOptUtil.copyRelHints(rel, newRel);
     setNewForOldRel(rel, newRel);
   }
 
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 e22cb50..8ed2017 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -767,8 +767,8 @@ public class SqlToRelConverter {
         }
       }
       rel =
-          LogicalProject.create(rel, Pair.left(newProjects),
-              Pair.right(newProjects));
+          LogicalProject.create(rel, ImmutableList.of(),
+              Pair.left(newProjects), Pair.right(newProjects));
       bb.root = rel;
       distinctify(bb, false);
       rel = bb.root;
@@ -787,8 +787,8 @@ public class SqlToRelConverter {
       }
 
       rel =
-          LogicalProject.create(rel, Pair.left(undoProjects),
-              Pair.right(undoProjects));
+          LogicalProject.create(rel, ImmutableList.of(),
+              Pair.left(undoProjects), Pair.right(undoProjects));
       bb.setRoot(
           rel,
           false);
@@ -863,7 +863,9 @@ public class SqlToRelConverter {
         exprs.add(rexBuilder.makeInputRef(bb.root, i));
       }
       bb.setRoot(
-          LogicalProject.create(bb.root, exprs,
+          LogicalProject.create(bb.root,
+              ImmutableList.of(),
+              exprs,
               rowType.getFieldNames().subList(0, fieldCount)),
           false);
     }
@@ -1166,7 +1168,10 @@ public class SqlToRelConverter {
         final int keyCount = leftKeys.size();
         final List<Integer> args = ImmutableIntList.range(0, keyCount);
         LogicalAggregate aggregate =
-            LogicalAggregate.create(seek, ImmutableBitSet.of(), null,
+            LogicalAggregate.create(seek,
+                ImmutableList.of(),
+                ImmutableBitSet.of(),
+                null,
                 ImmutableList.of(
                     AggregateCall.create(SqlStdOperatorTable.COUNT, false,
                         false, false, ImmutableList.of(), -1, 
RelCollations.EMPTY,
@@ -1174,8 +1179,8 @@ public class SqlToRelConverter {
                     AggregateCall.create(SqlStdOperatorTable.COUNT, false,
                         false, false, args, -1, RelCollations.EMPTY, longType, 
null)));
         LogicalJoin join =
-            LogicalJoin.create(bb.root, aggregate, 
rexBuilder.makeLiteral(true),
-                ImmutableSet.of(), JoinRelType.INNER);
+            LogicalJoin.create(bb.root, aggregate, ImmutableList.of(),
+                rexBuilder.makeLiteral(true), ImmutableSet.of(), 
JoinRelType.INNER);
         bb.setRoot(join, false);
       }
       final RexNode rex =
@@ -2397,12 +2402,15 @@ public class SqlToRelConverter {
       table = table.extend(extendedFields);
     }
     final RelNode tableRel;
-    final List<RelHint> hints = SqlUtil.getRelHint(hintStrategies, tableHints);
+    // Review Danny 2020-01-13: hacky to construct a new table scan
+    // in order to apply the hint strategies.
+    final List<RelHint> hints = hintStrategies.apply(
+        SqlUtil.getRelHint(hintStrategies, tableHints),
+        LogicalTableScan.create(cluster, table, ImmutableList.of()));
     if (config.isConvertTableAccess()) {
       tableRel = toRel(table, hints);
     } else {
-      tableRel = SqlUtil.attachRelHint(hintStrategies,
-          hints, LogicalTableScan.create(cluster, table));
+      tableRel = LogicalTableScan.create(cluster, table, hints);
     }
     bb.setRoot(tableRel, true);
     if (usedDataset[0]) {
@@ -2564,7 +2572,7 @@ public class SqlToRelConverter {
 
     final Join originalJoin =
         (Join) RelFactories.DEFAULT_JOIN_FACTORY.createJoin(leftRel, rightRel,
-            joinCond, ImmutableSet.of(), joinType, false);
+            ImmutableList.of(), joinCond, ImmutableSet.of(), joinType, false);
 
     RelNode node = RelOptUtil.pushDownJoinConditions(originalJoin, relBuilder);
     // If join conditions are pushed down, update the leaves.
@@ -3059,7 +3067,7 @@ public class SqlToRelConverter {
    */
   protected RelNode createAggregate(Blackboard bb, ImmutableBitSet groupSet,
       ImmutableList<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
-    return LogicalAggregate.create(bb.root, groupSet, groupSets, aggCalls);
+    return LogicalAggregate.create(bb.root, ImmutableList.of(), groupSet, 
groupSets, aggCalls);
   }
 
   public RexDynamicParam convertDynamicParam(
@@ -3382,10 +3390,7 @@ public class SqlToRelConverter {
   }
 
   public RelNode toRel(final RelOptTable table, @Nonnull final List<RelHint> 
hints) {
-    final RelNode rel = table.toRel(createToRelContext(hints));
-    final RelNode scan = rel instanceof Hintable && hints.size() > 0
-        ? SqlUtil.attachRelHint(hintStrategies, hints, (Hintable) rel)
-        : rel;
+    final RelNode scan = table.toRel(createToRelContext(hints));
 
     final InitializerExpressionFactory ief =
         Util.first(table.unwrap(InitializerExpressionFactory.class),
@@ -3965,6 +3970,7 @@ public class SqlToRelConverter {
           RelFactories.DEFAULT_JOIN_FACTORY.createJoin(
               ret,
               relNode,
+              ImmutableList.of(),
               rexBuilder.makeLiteral(true),
               ImmutableSet.of(),
               JoinRelType.INNER,
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 f31d892..7b240e6 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -53,6 +53,8 @@ import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.TableSpool;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.metadata.RelColumnMapping;
@@ -1071,7 +1073,7 @@ public class RelBuilder {
     if (relOptTable == null) {
       throw RESOURCE.tableNotFound(String.join(".", names)).ex();
     }
-    final RelNode scan = scanFactory.createScan(cluster, relOptTable);
+    final RelNode scan = scanFactory.createScan(cluster, relOptTable, 
ImmutableList.of());
     push(scan);
     rename(relOptTable.getRowType().getFieldNames());
 
@@ -1254,6 +1256,34 @@ public class RelBuilder {
     return project(nodes, fieldNames, false);
   }
 
+  /** Creates a {@link Project} of the given list
+   * of expressions, using the given names.
+   *
+   * <p>Names are deduced as follows:
+   * <ul>
+   *   <li>If the length of {@code fieldNames} is greater than the index of
+   *     the current entry in {@code nodes}, and the entry in
+   *     {@code fieldNames} is not null, uses it; otherwise
+   *   <li>If an expression projects an input field,
+   *     or is a cast an input field,
+   *     uses the input field name; otherwise
+   *   <li>If an expression is a call to
+   *     {@link SqlStdOperatorTable#AS}
+   *     (see {@link #alias}), removes the call but uses the intended alias.
+   * </ul>
+   *
+   * <p>After the field names have been inferred, makes the
+   * field names unique by appending numeric suffixes.
+   *
+   * @param nodes Expressions
+   * @param fieldNames Suggested field names
+   * @param force create project even if it is identity
+   */
+  public RelBuilder project(Iterable<? extends RexNode> nodes,
+      Iterable<String> fieldNames, boolean force) {
+    return project_(nodes, fieldNames, ImmutableList.of(), force);
+  }
+
   /** Creates a {@link Project} of all original fields, plus the given
    * expressions. */
   public RelBuilder projectPlus(RexNode... nodes) {
@@ -1319,11 +1349,13 @@ public class RelBuilder {
    *
    * @param nodes Expressions
    * @param fieldNames Suggested field names
+   * @param hints Hints
    * @param force create project even if it is identity
    */
-  public RelBuilder project(
+  private RelBuilder project_(
       Iterable<? extends RexNode> nodes,
       Iterable<String> fieldNames,
+      Iterable<RelHint> hints,
       boolean force) {
     final Frame frame = stack.peek();
     final RelDataType inputRowType = frame.rel.getRowType();
@@ -1379,7 +1411,10 @@ public class RelBuilder {
         }
       }
       stack.push(new Frame(project.getInput(), ImmutableList.copyOf(fields)));
-      return project(newNodes, fieldNameList, force);
+      final ImmutableSet.Builder<RelHint> mergedHints = ImmutableSet.builder();
+      mergedHints.addAll(project.getHints());
+      mergedHints.addAll(hints);
+      return project_(newNodes, fieldNameList, mergedHints.build(), force);
     }
 
     // Simplify expressions.
@@ -1439,12 +1474,15 @@ public class RelBuilder {
       } else {
         // create "virtual" row type for project only rename fields
         stack.pop();
+        // Ignore the hints.
         stack.push(new Frame(frame.rel, fields.build()));
-        return this;
       }
+      return this;
     }
     final RelNode project =
-        projectFactory.createProject(frame.rel, ImmutableList.copyOf(nodeList),
+        projectFactory.createProject(frame.rel,
+            ImmutableList.copyOf(hints),
+            ImmutableList.copyOf(nodeList),
             fieldNameList);
     stack.pop();
     stack.push(new Frame(project, fields.build()));
@@ -1497,7 +1535,7 @@ public class RelBuilder {
         final Project childProject = (Project) frame.rel;
         final Project newInput = childProject.copy(childProject.getTraitSet(),
             childProject.getInput(), childProject.getProjects(), rowType);
-        stack.push(new Frame(newInput, frame.fields));
+        stack.push(new Frame(newInput.attachHints(childProject.getHints()), 
frame.fields));
       }
     } else {
       project(nodeList, rowType.getFieldNames(), force);
@@ -1590,7 +1628,7 @@ public class RelBuilder {
             .collect(Collectors.toList()));
   }
 
-  /** Creates an {@link Aggregate} with a list of
+  /** Creates an {@link Aggregate} with multiple
    * calls. */
   public RelBuilder aggregate(GroupKey groupKey, Iterable<AggCall> aggCalls) {
     final Registrar registrar =
@@ -1736,7 +1774,7 @@ public class RelBuilder {
       List<AggregateCall> aggregateCalls, List<RexNode> extraNodes,
       ImmutableList<Field> inFields) {
     final RelNode aggregate = aggregateFactory.createAggregate(input,
-        groupSet, groupSets, aggregateCalls);
+        ImmutableList.of(), groupSet, groupSets, aggregateCalls);
 
     // build field list
     final ImmutableList.Builder<Field> fields = ImmutableList.builder();
@@ -1878,7 +1916,7 @@ public class RelBuilder {
         rowType,
         transientTable,
         ImmutableList.of(tableName));
-    RelNode scan = scanFactory.createScan(cluster, relOptTable);
+    RelNode scan = scanFactory.createScan(cluster, relOptTable, 
ImmutableList.of());
     push(scan);
     rename(rowType.getFieldNames());
     return this;
@@ -1973,7 +2011,7 @@ public class RelBuilder {
     }
   }
 
-  /** Creates a {@link Join}. */
+  /** Creates a {@link Join} with an array of conditions. */
   public RelBuilder join(JoinRelType joinType, RexNode condition0,
       RexNode... conditions) {
     return join(joinType, Lists.asList(condition0, conditions));
@@ -1987,12 +2025,12 @@ public class RelBuilder {
         ImmutableSet.of());
   }
 
+  /** Creates a {@link Join} with one condition. */
   public RelBuilder join(JoinRelType joinType, RexNode condition) {
     return join(joinType, condition, ImmutableSet.of());
   }
 
-  /** Creates a {@link Join} with correlating
-   * variables. */
+  /** Creates a {@link Join} with correlating variables. */
   public RelBuilder join(JoinRelType joinType, RexNode condition,
       Set<CorrelationId> variablesSet) {
     Frame right = stack.pop();
@@ -2032,7 +2070,7 @@ public class RelBuilder {
       join = correlateFactory.createCorrelate(left.rel, right.rel, id,
           requiredColumns, joinType);
     } else {
-      join = joinFactory.createJoin(left.rel, right.rel, condition,
+      join = joinFactory.createJoin(left.rel, right.rel, ImmutableList.of(), 
condition,
           variablesSet, joinType, false);
     }
     final ImmutableList.Builder<Field> fields = ImmutableList.builder();
@@ -2118,8 +2156,13 @@ public class RelBuilder {
   public RelBuilder semiJoin(Iterable<? extends RexNode> conditions) {
     final Frame right = stack.pop();
     final RelNode semiJoin =
-        joinFactory.createJoin(peek(), right.rel,
-            and(conditions), ImmutableSet.of(), JoinRelType.SEMI, false);
+        joinFactory.createJoin(peek(),
+            right.rel,
+            ImmutableList.of(),
+            and(conditions),
+            ImmutableSet.of(),
+            JoinRelType.SEMI,
+            false);
     replaceTop(semiJoin);
     return this;
   }
@@ -2150,8 +2193,13 @@ public class RelBuilder {
   public RelBuilder antiJoin(Iterable<? extends RexNode> conditions) {
     final Frame right = stack.pop();
     final RelNode antiJoin =
-        joinFactory.createJoin(peek(), right.rel,
-            and(conditions), ImmutableSet.of(), JoinRelType.ANTI, false);
+        joinFactory.createJoin(peek(),
+            right.rel,
+            ImmutableList.of(),
+            and(conditions),
+            ImmutableSet.of(),
+            JoinRelType.ANTI,
+            false);
     replaceTop(antiJoin);
     return this;
   }
@@ -2431,6 +2479,7 @@ public class RelBuilder {
                     offsetNode, fetchNode);
             replaceTop(
                 projectFactory.createProject(sort,
+                    project.getHints(),
                     project.getProjects(),
                     Pair.right(project.getNamedProjects())));
             return this;
@@ -2556,6 +2605,40 @@ public class RelBuilder {
     return this;
   }
 
+  /**
+   * Attaches an array of hints to the stack top relational expression.
+   *
+   * <p>The redundant hints would be eliminated.
+   *
+   * @param hints Hints
+   *
+   * @throws AssertionError if the top relational expression does not implement
+   * {@link org.apache.calcite.rel.hint.Hintable}
+   */
+  public RelBuilder hints(RelHint... hints) {
+    return hints(ImmutableList.copyOf(hints));
+  }
+
+  /**
+   * Attaches multiple hints to the stack top relational expression.
+   *
+   * <p>The redundant hints would be eliminated.
+   *
+   * @param hints Hints
+   *
+   * @throws AssertionError if the top relational expression does not implement
+   * {@link org.apache.calcite.rel.hint.Hintable}
+   */
+  public RelBuilder hints(Iterable<RelHint> hints) {
+    Objects.requireNonNull(hints);
+    final Frame frame = peek_();
+    assert frame != null : "There is no relational expression to attach the 
hints";
+    assert frame.rel instanceof Hintable : "The top relational expression is 
not a Hintable";
+    Hintable hintable = (Hintable) frame.rel;
+    replaceTop(hintable.attachHints(ImmutableList.copyOf(hints)));
+    return this;
+  }
+
   /** Clears the stack.
    *
    * <p>The builder's state is now the same as when it was created. */
diff --git a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java 
b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
index bd49d78..e4dd6d0 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
@@ -610,6 +610,7 @@ public class RelOptUtilTest {
         .DEFAULT_PROJECT_FACTORY
         .createProject(
             agg,
+            ImmutableList.of(),
             ImmutableList.of(
                 RexInputRef.of(0, agg.getRowType()),
                 RexInputRef.of(1, agg.getRowType()),
@@ -632,6 +633,7 @@ public class RelOptUtilTest {
         .DEFAULT_PROJECT_FACTORY
         .createProject(
             agg,
+            ImmutableList.of(),
             ImmutableList.of(
                 RexInputRef.of(0, agg.getRowType()),
                 RexInputRef.of(1, agg.getRowType()),
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java 
b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index f0362f4..1714f2e 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -362,7 +362,8 @@ public class RelWriterTest {
           LogicalTableScan scan =
               LogicalTableScan.create(cluster,
                   relOptSchema.getTableForMember(
-                      Arrays.asList("hr", "emps")));
+                      Arrays.asList("hr", "emps")),
+                  ImmutableList.of());
           final RexBuilder rexBuilder = cluster.getRexBuilder();
           LogicalFilter filter =
               LogicalFilter.create(scan,
@@ -376,7 +377,10 @@ public class RelWriterTest {
           final RelDataType bigIntType =
               cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
           LogicalAggregate aggregate =
-              LogicalAggregate.create(filter, ImmutableBitSet.of(0), null,
+              LogicalAggregate.create(filter,
+                  ImmutableList.of(),
+                  ImmutableBitSet.of(0),
+                  null,
                   ImmutableList.of(
                       AggregateCall.create(SqlStdOperatorTable.COUNT,
                           true, false, false, ImmutableList.of(1), -1,
@@ -403,12 +407,14 @@ public class RelWriterTest {
           LogicalTableScan scan =
               LogicalTableScan.create(cluster,
                   relOptSchema.getTableForMember(
-                      Arrays.asList("hr", "emps")));
+                      Arrays.asList("hr", "emps")),
+                  ImmutableList.of());
           final RexBuilder rexBuilder = cluster.getRexBuilder();
           final RelDataType bigIntType =
               cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
           LogicalProject project =
               LogicalProject.create(scan,
+                  ImmutableList.of(),
                   ImmutableList.of(
                       rexBuilder.makeInputRef(scan, 0),
                       rexBuilder.makeOver(bigIntType,
diff --git 
a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java 
b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
index 5394cd0..bb3918c 100644
--- 
a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
+++ 
b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
@@ -153,10 +153,11 @@ public class TraitPropagationTest {
         }
       };
 
-      final RelNode rt1 = LogicalTableScan.create(cluster, t1);
+      final RelNode rt1 = LogicalTableScan.create(cluster, t1, 
ImmutableList.of());
 
       // project s column
       RelNode project = LogicalProject.create(rt1,
+          ImmutableList.of(),
           ImmutableList.of(
               (RexNode) rexBuilder.makeInputRef(stringType, 0),
               rexBuilder.makeInputRef(integerType, 1)),
diff --git a/core/src/test/java/org/apache/calcite/test/Matchers.java 
b/core/src/test/java/org/apache/calcite/test/Matchers.java
index d22024c..2acd22a 100644
--- a/core/src/test/java/org/apache/calcite/test/Matchers.java
+++ b/core/src/test/java/org/apache/calcite/test/Matchers.java
@@ -18,6 +18,7 @@ package org.apache.calcite.test;
 
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.hint.Hintable;
 import org.apache.calcite.util.TestUtil;
 import org.apache.calcite.util.Util;
 
@@ -205,6 +206,17 @@ public class Matchers {
   }
 
   /**
+   * Creates a Matcher that matches a {@link RelNode} if its hints string
+   * representation is equal to the given {@code value}.
+   */
+  public static Matcher<RelNode> hasHints(final String value) {
+    return compose(Is.is(value),
+        input -> input instanceof Hintable
+            ? ((Hintable) input).getHints().toString()
+            : "[]");
+  }
+
+  /**
    * Creates a {@link Matcher} that matches execution plan and trims {@code , 
id=123} node ids.
    * {@link RelNode#getId()} is not stable across runs, so this matcher 
enables to trim those.
    * @param value execpted execution plan
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 e5468dc..471152e 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -33,6 +33,7 @@ import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.TableFunctionScan;
 import org.apache.calcite.rel.core.TableModify;
 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.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
@@ -83,6 +84,7 @@ import java.util.TreeSet;
 import java.util.function.Function;
 import javax.annotation.Nonnull;
 
+import static org.apache.calcite.test.Matchers.hasHints;
 import static org.apache.calcite.test.Matchers.hasTree;
 
 import static org.hamcrest.CoreMatchers.allOf;
@@ -3051,4 +3053,91 @@ public class RelBuilderTest {
         + "    LogicalTableScan(table=[[scott, DEPT]])\n";
     assertThat(root, hasTree(expected));
   }
+
+  @Test public void testHints() {
+    final RelHint indexHint = RelHint.of(Collections.emptyList(),
+        "INDEX",
+        Arrays.asList("_idx1", "_idx2"));
+    final RelHint propsHint = RelHint.of(Collections.singletonList(0),
+        "PROPERTIES",
+        ImmutableMap.of("parallelism", "3", "mem", "20Mb"));
+    final RelHint noHashJoinHint = RelHint.of(Collections.singletonList(0),
+        "NO_HASH_JOIN");
+    final RelBuilder builder = RelBuilder.create(config().build());
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM emp /*+ INDEX(_idx1, _idx2) */
+    final RelNode root = builder
+            .scan("EMP")
+            .hints(indexHint)
+            .build();
+    assertThat(root,
+        hasHints("[[INDEX inheritPath:[] options:[_idx1, _idx2]]]"));
+    // Equivalent SQL:
+    //   SELECT /*+  PROPERTIES(parallelism='3', mem='20Mb') */
+    //   *
+    //   FROM emp /*+ INDEX(_idx1, _idx2) */
+    final RelNode root1 = builder
+            .scan("EMP")
+            .hints(indexHint, propsHint)
+            .build();
+    assertThat(root1,
+        hasHints("[[INDEX inheritPath:[] options:[_idx1, _idx2]], "
+            + "[PROPERTIES inheritPath:[0] options:{parallelism=3, 
mem=20Mb}]]"));
+    // Equivalent SQL:
+    //   SELECT /*+ NO_HASH_JOIN */
+    //   *
+    //   FROM emp
+    //     join dept
+    //     on emp.deptno = dept.deptno
+    final RelNode root2 = builder
+        .scan("EMP")
+        .scan("DEPT")
+        .join(JoinRelType.INNER,
+            builder.equals(
+                builder.field(2, 0, "DEPTNO"),
+                builder.field(2, 1, "DEPTNO")))
+        .hints(noHashJoinHint)
+        .build();
+    assertThat(root2, hasHints("[[NO_HASH_JOIN inheritPath:[0]]]"));
+  }
+
+  @Test public void testHintsOnEmptyStack() {
+    final RelHint indexHint = RelHint.of(Collections.emptyList(),
+        "INDEX",
+        Arrays.asList("_idx1", "_idx2"));
+    // Attach hints on empty stack.
+    final AssertionError error = assertThrows(
+        AssertionError.class,
+        () -> RelBuilder.create(config().build()).hints(indexHint),
+        "hints() should fail on empty stack");
+    assertThat(error.getMessage(),
+        containsString("There is no relational expression to attach the 
hints"));
+  }
+
+  @Test public void testHintsOnNonHintable() {
+    final RelHint indexHint = RelHint.of(Collections.emptyList(),
+        "INDEX",
+        Arrays.asList("_idx1", "_idx2"));
+    // Attach hints on non hintable.
+    final AssertionError error1 = assertThrows(
+        AssertionError.class,
+        () -> {
+          final RelBuilder builder = RelBuilder.create(config().build());
+          // Equivalent SQL:
+          //   SELECT *
+          //   FROM emp
+          //   WHERE EMPNO = 124
+          builder
+              .scan("EMP")
+              .filter(
+                  builder.equals(
+                      builder.field("EMPNO"),
+                      builder.literal(124)))
+              .hints(indexHint);
+        },
+        "hints() should fail on non Hintable relational expression");
+    assertThat(error1.getMessage(),
+        containsString("The top relational expression is not a Hintable"));
+  }
 }
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java 
b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 29ecb5d..2b84ae2 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -1468,7 +1468,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
   private void checkCollation(RelOptCluster cluster, RelOptTable empTable,
       RelOptTable deptTable) {
     final RexBuilder rexBuilder = cluster.getRexBuilder();
-    final LogicalTableScan empScan = LogicalTableScan.create(cluster, 
empTable);
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, 
empTable, ImmutableList.of());
 
     List<RelCollation> collations =
         RelMdCollation.table(empScan.getTable());
@@ -1500,11 +1500,13 @@ public class RelMetadataTest extends SqlToRelTestBase {
     assertThat(collations.get(0).getFieldCollations().get(1).getFieldIndex(),
         equalTo(0));
 
-    final LogicalProject project = LogicalProject.create(empSort, projects,
+    final LogicalProject project = LogicalProject.create(empSort,
+        ImmutableList.of(),
+        projects,
         ImmutableList.of("a", "b", "c", "d"));
 
     final LogicalTableScan deptScan =
-        LogicalTableScan.create(cluster, deptTable);
+        LogicalTableScan.create(cluster, deptTable, ImmutableList.of());
 
     final RelCollation deptCollation =
         RelCollations.of(new RelFieldCollation(0), new RelFieldCollation(1));
@@ -1653,7 +1655,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
       RelOptTable deptTable) {
     final RexBuilder rexBuilder = cluster.getRexBuilder();
     final RelMetadataQuery mq = cluster.getMetadataQuery();
-    final LogicalTableScan empScan = LogicalTableScan.create(cluster, 
empTable);
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, 
empTable, ImmutableList.of());
 
     Double rowSize = mq.getAverageRowSize(empScan);
     List<Double> columnSizes = mq.getAverageColumnSizes(empScan);
@@ -1708,7 +1710,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
 
     // Filter
     final LogicalTableScan deptScan =
-        LogicalTableScan.create(cluster, deptTable);
+        LogicalTableScan.create(cluster, deptTable, ImmutableList.of());
     final LogicalFilter filter =
         LogicalFilter.create(deptScan,
             rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN,
@@ -1723,6 +1725,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
     // Project
     final LogicalProject deptProject =
         LogicalProject.create(filter,
+            ImmutableList.of(),
             ImmutableList.of(
                 rexBuilder.makeInputRef(filter, 0),
                 rexBuilder.makeInputRef(filter, 1),
@@ -1740,8 +1743,8 @@ public class RelMetadataTest extends SqlToRelTestBase {
 
     // Join
     final LogicalJoin join =
-        LogicalJoin.create(empScan, deptProject, rexBuilder.makeLiteral(true),
-            ImmutableSet.of(), JoinRelType.INNER);
+        LogicalJoin.create(empScan, deptProject, ImmutableList.of(),
+            rexBuilder.makeLiteral(true), ImmutableSet.of(), 
JoinRelType.INNER);
     rowSize = mq.getAverageRowSize(join);
     columnSizes = mq.getAverageColumnSizes(join);
     assertThat(columnSizes.size(), equalTo(13));
@@ -1753,7 +1756,9 @@ public class RelMetadataTest extends SqlToRelTestBase {
 
     // Aggregate
     final LogicalAggregate aggregate =
-        LogicalAggregate.create(join, ImmutableBitSet.of(2, 0),
+        LogicalAggregate.create(join,
+            ImmutableList.of(),
+            ImmutableBitSet.of(2, 0),
             ImmutableList.of(),
             ImmutableList.of(
                 AggregateCall.create(SqlStdOperatorTable.COUNT,
@@ -1793,7 +1798,8 @@ public class RelMetadataTest extends SqlToRelTestBase {
     final RelBuilder relBuilder = RelBuilder.proto().create(cluster, null);
     final RelMetadataQuery mq = cluster.getMetadataQuery();
 
-    final LogicalTableScan empScan = LogicalTableScan.create(cluster, 
empTable);
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, empTable,
+        ImmutableList.of());
     relBuilder.push(empScan);
 
     RelOptPredicateList predicates =
@@ -1809,7 +1815,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
     assertThat(predicates.pulledUpPredicates, sortsAs("[=($0, 1)]"));
 
     final LogicalTableScan deptScan =
-        LogicalTableScan.create(cluster, deptTable);
+        LogicalTableScan.create(cluster, deptTable, ImmutableList.of());
     relBuilder.push(deptScan);
 
     relBuilder.semiJoin(
@@ -2396,7 +2402,8 @@ public class RelMetadataTest extends SqlToRelTestBase {
     final RelBuilder relBuilder = RelBuilder.proto().create(cluster, null);
     final RelMetadataQuery mq = cluster.getMetadataQuery();
 
-    final LogicalTableScan empScan = LogicalTableScan.create(cluster, 
empTable);
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, empTable,
+        ImmutableList.of());
     relBuilder.push(empScan);
 
     RelOptPredicateList predicates =
@@ -2417,7 +2424,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
     assertThat(inputRef1.getIndex(), is(0));
 
     final LogicalTableScan deptScan =
-        LogicalTableScan.create(cluster, deptTable);
+        LogicalTableScan.create(cluster, deptTable, ImmutableList.of());
     relBuilder.push(deptScan);
 
     relBuilder.join(JoinRelType.INNER,
@@ -2602,8 +2609,8 @@ public class RelMetadataTest extends SqlToRelTestBase {
     final RexBuilder rexBuilder = node.getCluster().getRexBuilder();
     // Join
     final LogicalJoin join =
-        LogicalJoin.create(nodeWithUnknown, node, rexBuilder.makeLiteral(true),
-            ImmutableSet.of(), JoinRelType.INNER);
+        LogicalJoin.create(nodeWithUnknown, node, ImmutableList.of(),
+            rexBuilder.makeLiteral(true), ImmutableSet.of(), 
JoinRelType.INNER);
     final RelMetadataQuery mq = node.getCluster().getMetadataQuery();
     final Set<RelTableRef> tableReferences = mq.getTableReferences(join);
     assertNull(tableReferences);
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 48801d3..e94a271 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
@@ -90,6 +90,7 @@ import static org.hamcrest.collection.IsIn.in;
 import static org.hamcrest.core.Is.is;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
  * Unit test for {@link org.apache.calcite.rel.hint.RelHint}.
@@ -477,10 +478,11 @@ public class SqlHintsConverterTest extends 
SqlToRelTestBase {
       assertThat(1, is(join.getHints().size()));
       call.transformTo(
           LogicalJoin.create(join.getLeft(),
-            join.getRight(),
-            join.getCondition(),
-            join.getVariablesSet(),
-            join.getJoinType()));
+              join.getRight(),
+              ImmutableList.of(),
+              join.getCondition(),
+              join.getVariablesSet(),
+              join.getJoinType()));
     }
   }
 
@@ -599,6 +601,7 @@ public class SqlHintsConverterTest extends SqlToRelTestBase 
{
     void fails(String failedMsg) {
       try {
         tester.convertSqlToRel(sql);
+        fail("Unexpected exception");
       } catch (AssertionError e) {
         assertThat(e.getMessage(), is(failedMsg));
       }
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 edf6735..c79019c 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
@@ -411,7 +411,8 @@ public abstract class SqlToRelTestBase {
       }
 
       public RelNode toRel(ToRelContext context) {
-        return LogicalTableScan.create(context.getCluster(), this);
+        return LogicalTableScan.create(context.getCluster(), this,
+            context.getTableHints());
       }
 
       public List<RelCollation> getCollationList() {
@@ -493,7 +494,8 @@ public abstract class SqlToRelTestBase {
     }
 
     public RelNode toRel(ToRelContext context) {
-      return LogicalTableScan.create(context.getCluster(), this);
+      return LogicalTableScan.create(context.getCluster(), this,
+          context.getTableHints());
     }
 
     public List<RelCollation> getCollationList() {
diff --git 
a/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReader.java 
b/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReader.java
index 05b9b19..fcad4d1 100644
--- a/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReader.java
@@ -493,7 +493,7 @@ public abstract class MockCatalogReader extends 
CalciteCatalogReader {
     }
 
     public RelNode toRel(ToRelContext context) {
-      return LogicalTableScan.create(context.getCluster(), this);
+      return LogicalTableScan.create(context.getCluster(), this, 
context.getTableHints());
     }
 
     public List<RelCollation> getCollationList() {
@@ -864,7 +864,8 @@ public abstract class MockCatalogReader extends 
CalciteCatalogReader {
     }
 
     @Override public RelNode toRel(ToRelContext context) {
-      RelNode rel = LogicalTableScan.create(context.getCluster(), fromTable);
+      RelNode rel = LogicalTableScan.create(context.getCluster(), fromTable,
+          context.getTableHints());
       final RexBuilder rexBuilder = context.getCluster().getRexBuilder();
       rel = LogicalFilter.create(
           rel, getConstraint(rexBuilder, rel.getRowType()));
@@ -880,7 +881,9 @@ public abstract class MockCatalogReader extends 
CalciteCatalogReader {
               return mapping.size();
             }
           };
-      return LogicalProject.create(rel, Pair.left(projects),
+      return LogicalProject.create(rel,
+          ImmutableList.of(),
+          Pair.left(projects),
           Pair.right(projects));
     }
 
@@ -919,7 +922,7 @@ public abstract class MockCatalogReader extends 
CalciteCatalogReader {
     }
 
     @Override public RelNode toRel(RelOptTable.ToRelContext context, 
RelOptTable relOptTable) {
-      return LogicalTableScan.create(context.getCluster(), relOptTable);
+      return LogicalTableScan.create(context.getCluster(), relOptTable, 
context.getTableHints());
     }
   }
 
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 04f247d..d9eb8ef 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlHintsConverterTest.xml
@@ -23,7 +23,7 @@ from (select /*+ resource(mem='20Mb')*/ empno, ename from 
emp)]]>
         </Resource>
         <Resource name="hints">
             <![CDATA[
-Project:[[RESOURCE inheritPath:[] options:{PARALLELISM=3}], [REPARTITION 
inheritPath:[] options:[10]]]
+Project:[[RESOURCE inheritPath:[] options:{MEM=20Mb}], [RESOURCE 
inheritPath:[] options:{PARALLELISM=3}], [REPARTITION inheritPath:[] 
options:[10]]]
 ]]>
         </Resource>
     </TestCase>
@@ -165,7 +165,7 @@ from emp left join dept on emp.deptno = dept.deptno)]]>
         </Resource>
         <Resource name="hints">
             <![CDATA[
-Project:[[RESOURCE inheritPath:[] options:{PARALLELISM=3}], [NO_HASH_JOIN 
inheritPath:[]]]
+Project:[[RESOURCE inheritPath:[] options:{MEM=20Mb}], [RESOURCE 
inheritPath:[] options:{PARALLELISM=3}], [NO_HASH_JOIN inheritPath:[]]]
 LogicalJoin:[[NO_HASH_JOIN inheritPath:[0]]]
 ]]>
         </Resource>
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
index e7ef4b0..9c9f43c 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
@@ -242,7 +242,7 @@ public class DruidTable extends AbstractTable implements 
TranslatableTable {
   public RelNode toRel(RelOptTable.ToRelContext context,
       RelOptTable relOptTable) {
     final RelOptCluster cluster = context.getCluster();
-    final TableScan scan = LogicalTableScan.create(cluster, relOptTable);
+    final TableScan scan = LogicalTableScan.create(cluster, relOptTable, 
ImmutableList.of());
     return DruidQuery.create(cluster,
         cluster.traitSetOf(BindableConvention.INSTANCE), relOptTable, this,
         ImmutableList.of(scan));
diff --git 
a/pig/src/main/java/org/apache/calcite/adapter/pig/PigRelFactories.java 
b/pig/src/main/java/org/apache/calcite/adapter/pig/PigRelFactories.java
index e1c3050..6cccb6a 100644
--- a/pig/src/main/java/org/apache/calcite/adapter/pig/PigRelFactories.java
+++ b/pig/src/main/java/org/apache/calcite/adapter/pig/PigRelFactories.java
@@ -26,8 +26,10 @@ import org.apache.calcite.rel.core.AggregateCall;
 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.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Util;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -58,7 +60,9 @@ public class PigRelFactories {
 
     public static final PigTableScanFactory INSTANCE = new 
PigTableScanFactory();
 
-    @Override public RelNode createScan(RelOptCluster cluster, RelOptTable 
table) {
+    @Override public RelNode createScan(RelOptCluster cluster,
+        RelOptTable table, List<RelHint> hints) {
+      Util.discard(hints);
       return new PigTableScan(cluster, cluster.traitSetOf(PigRel.CONVENTION), 
table);
     }
   }
@@ -92,8 +96,10 @@ public class PigRelFactories {
     public static final PigAggregateFactory INSTANCE = new 
PigAggregateFactory();
 
     @Override public RelNode createAggregate(RelNode input,
+        List<RelHint> hints,
         ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
         List<AggregateCall> aggCalls) {
+      Util.discard(hints);
       return new PigAggregate(input.getCluster(), input.getTraitSet(), input,
           groupSet, groupSets, aggCalls);
     }
@@ -108,8 +114,12 @@ public class PigRelFactories {
 
     public static final PigJoinFactory INSTANCE = new PigJoinFactory();
 
-    @Override public RelNode createJoin(RelNode left, RelNode right, RexNode 
condition,
-        Set<CorrelationId> variablesSet, JoinRelType joinType, boolean 
semiJoinDone) {
+    @Override public RelNode createJoin(RelNode left, RelNode right, 
List<RelHint> hints,
+        RexNode condition, Set<CorrelationId> variablesSet, JoinRelType 
joinType,
+        boolean semiJoinDone) {
+      Util.discard(hints);
+      Util.discard(variablesSet);
+      Util.discard(semiJoinDone);
       return new PigJoin(left.getCluster(), left.getTraitSet(), left, right, 
condition, joinType);
     }
 
diff --git a/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java 
b/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java
index e85d5b3..96150af 100644
--- a/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java
+++ b/piglet/src/main/java/org/apache/calcite/piglet/PigRelBuilder.java
@@ -78,7 +78,7 @@ public class PigRelBuilder extends RelBuilder {
   /** Creates a PigRelBuilder. */
   public static PigRelBuilder create(FrameworkConfig config) {
     final RelBuilder relBuilder = RelBuilder.create(config);
-    Hook.REL_BUILDER_SIMPLIFY.add(Hook.propertyJ(false));
+    Hook.REL_BUILDER_SIMPLIFY.addThread(Hook.propertyJ(false));
     return new PigRelBuilder(config.getContext(), relBuilder.getCluster(),
         relBuilder.getRelOptSchema());
   }
@@ -253,7 +253,7 @@ public class PigRelBuilder extends RelBuilder {
    * @return This builder
    */
   private RelBuilder scan(RelOptTable tableSchema) {
-    final RelNode scan = getScanFactory().createScan(cluster, tableSchema);
+    final RelNode scan = getScanFactory().createScan(cluster, tableSchema, 
ImmutableList.of());
     push(scan);
     return this;
   }
diff --git a/site/_docs/history.md b/site/_docs/history.md
index b24c767..cc842df 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -33,6 +33,7 @@ Downloads are available on the
 #### Breaking Changes
 
 * Constructors for `Project`, `TableScan`, `Calc`, `Aggregate` and `Join` 
introduce new parameter named "hints";
+* Logical `RelNode`'s `create` method need to pass in hints explicitly;
 * `Project` names will not represent in `RelNode` digest anymore;
 * `RexCall`s are default to be normalized in the `RelNode` digest. 
 
diff --git 
a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
 
b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
index 2163d8e..10ca4f8 100644
--- 
a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
+++ 
b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
@@ -228,7 +228,7 @@ public class SplunkPushDownRule
     // handle top projection (ie reordering and renaming)
     List<RelDataTypeField> newFields = bottomFields;
     if (topProj != null) {
-      LOGGER.debug("topProj: {}", String.valueOf(topProj.getPermutation()));
+      LOGGER.debug("topProj: {}", topProj.getPermutation());
       newFields = new ArrayList<>();
       int i = 0;
       for (RexNode rn : topProj.getProjects()) {
@@ -279,7 +279,8 @@ public class SplunkPushDownRule
     if (proj == null) {
       return rel;
     }
-    return LogicalProject.create(rel, proj.getProjects(), proj.getRowType());
+    return LogicalProject.create(rel, proj.getHints(),
+        proj.getProjects(), proj.getRowType());
   }
 
   // TODO: use StringBuilder instead of String

Reply via email to