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 09a8a2f  [CALCITE-4640] Propagate table scan hints to JDBC
09a8a2f is described below

commit 09a8a2f74c31e215dfee02c1abdca8d70753157f
Author: Ulrich Kramer <[email protected]>
AuthorDate: Fri Jun 4 15:08:13 2021 +0200

    [CALCITE-4640] Propagate table scan hints to JDBC
    
    close apache/calcite#2426
---
 .../calcite/adapter/jdbc/JdbcImplementor.java      | 10 ------
 .../org/apache/calcite/adapter/jdbc/JdbcTable.java |  2 +-
 .../apache/calcite/adapter/jdbc/JdbcTableScan.java | 15 +++++++--
 .../calcite/rel/rel2sql/RelToSqlConverter.java     | 36 +++++++++++++++++++++-
 .../apache/calcite/rel/rel2sql/SqlImplementor.java |  2 ++
 .../java/org/apache/calcite/sql/SqlDialect.java    |  5 +++
 .../java/org/apache/calcite/sql/SqlTableRef.java   |  5 +--
 .../apache/calcite/sql/dialect/AnsiSqlDialect.java | 12 ++++++++
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 31 +++++++++++++++++++
 9 files changed, 100 insertions(+), 18 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java 
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java
index 83e5f64..0695a77 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java
@@ -22,8 +22,6 @@ import org.apache.calcite.rel.rel2sql.RelToSqlConverter;
 import org.apache.calcite.sql.SqlDialect;
 import org.apache.calcite.util.Util;
 
-import com.google.common.collect.ImmutableList;
-
 /**
  * State for generating a SQL statement.
  */
@@ -33,14 +31,6 @@ public class JdbcImplementor extends RelToSqlConverter {
     Util.discard(typeFactory);
   }
 
-  // CHECKSTYLE: IGNORE 1
-  /** @see #dispatch */
-  @SuppressWarnings("MissingSummary")
-  public Result visit(JdbcTableScan scan) {
-    return result(scan.jdbcTable.tableName(),
-        ImmutableList.of(Clause.FROM), scan, null);
-  }
-
   public Result implement(RelNode node) {
     return dispatch(node);
   }
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java 
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
index 06b59e5..7b2e99a 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
@@ -175,7 +175,7 @@ public class JdbcTable extends AbstractQueryableTable
 
   @Override public RelNode toRel(RelOptTable.ToRelContext context,
       RelOptTable relOptTable) {
-    return new JdbcTableScan(context.getCluster(), relOptTable, this,
+    return new JdbcTableScan(context.getCluster(), context.getTableHints(), 
relOptTable, this,
         jdbcSchema.convention);
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java 
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java
index 6e104d6..7cab213 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java
@@ -16,11 +16,13 @@
  */
 package org.apache.calcite.adapter.jdbc;
 
+import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.hint.RelHint;
 
 import com.google.common.collect.ImmutableList;
 
@@ -29,6 +31,8 @@ import java.util.Objects;
 
 import static org.apache.calcite.linq4j.Nullness.castNonNull;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Relational expression representing a scan of a table in a JDBC data source.
  */
@@ -37,21 +41,28 @@ public class JdbcTableScan extends TableScan implements 
JdbcRel {
 
   protected JdbcTableScan(
       RelOptCluster cluster,
+      List<RelHint> hints,
       RelOptTable table,
       JdbcTable jdbcTable,
       JdbcConvention jdbcConvention) {
-    super(cluster, cluster.traitSetOf(jdbcConvention), ImmutableList.of(), 
table);
+    super(cluster, cluster.traitSetOf(jdbcConvention), hints, table);
     this.jdbcTable = Objects.requireNonNull(jdbcTable, "jdbcTable");
   }
 
   @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
     assert inputs.isEmpty();
     return new JdbcTableScan(
-        getCluster(), table, jdbcTable, (JdbcConvention) 
castNonNull(getConvention()));
+        getCluster(), getHints(), table, jdbcTable, (JdbcConvention) 
castNonNull(getConvention()));
   }
 
   @Override public JdbcImplementor.Result implement(JdbcImplementor 
implementor) {
     return implementor.result(jdbcTable.tableName(),
         ImmutableList.of(JdbcImplementor.Clause.FROM), this, null);
   }
+
+  @Override public RelNode withHints(List<RelHint> hintList) {
+    Convention convention = requireNonNull(getConvention(), "getConvention()");
+    return new JdbcTableScan(getCluster(), hintList, getTable(), jdbcTable,
+        (JdbcConvention) convention);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java 
b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
index bd73cf4..c9fb0eb 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
@@ -44,6 +44,7 @@ import org.apache.calcite.rel.core.Uncollect;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.core.Window;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSort;
 import org.apache.calcite.rel.type.RelDataType;
@@ -61,6 +62,7 @@ import org.apache.calcite.sql.SqlBasicCall;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDelete;
 import org.apache.calcite.sql.SqlDialect;
+import org.apache.calcite.sql.SqlHint;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlInsert;
 import org.apache.calcite.sql.SqlIntervalLiteral;
@@ -71,6 +73,7 @@ import org.apache.calcite.sql.SqlMatchRecognize;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlTableRef;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.fun.SqlInternalOperators;
@@ -106,6 +109,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.apache.calcite.rex.RexLiteral.stringValue;
 
@@ -525,7 +529,37 @@ public class RelToSqlConverter extends SqlImplementor
   /** Visits a TableScan; called by {@link #dispatch} via reflection. */
   public Result visit(TableScan e) {
     final SqlIdentifier identifier = getSqlTargetTable(e);
-    return result(identifier, ImmutableList.of(Clause.FROM), e, null);
+    final SqlNode node;
+    final ImmutableList<RelHint> hints = e.getHints();
+    if (!hints.isEmpty()) {
+      SqlParserPos pos = identifier.getParserPosition();
+      node = new SqlTableRef(pos, identifier,
+          SqlNodeList.of(pos, hints.stream().map(h -> 
RelToSqlConverter.toSqlHint(h, pos))
+              .collect(Collectors.toList())));
+    } else {
+      node = identifier;
+    }
+    return result(node, ImmutableList.of(Clause.FROM), e, null);
+  }
+
+  private static SqlHint toSqlHint(RelHint hint, SqlParserPos pos) {
+    if (hint.kvOptions != null) {
+      return new SqlHint(pos, new SqlIdentifier(hint.hintName, pos),
+          SqlNodeList.of(pos, hint.kvOptions.entrySet().stream()
+              .flatMap(
+                  e -> Stream.of(new SqlIdentifier(e.getKey(), pos),
+                  SqlLiteral.createCharString(e.getValue(), pos)))
+              .collect(Collectors.toList())),
+          SqlHint.HintOptionFormat.KV_LIST);
+    } else if (hint.listOptions != null) {
+      return new SqlHint(pos, new SqlIdentifier(hint.hintName, pos),
+          SqlNodeList.of(pos, hint.listOptions.stream()
+              .map(e -> SqlLiteral.createCharString(e, pos))
+              .collect(Collectors.toList())),
+          SqlHint.HintOptionFormat.LITERAL_LIST);
+    }
+    return new SqlHint(pos, new SqlIdentifier(hint.hintName, pos),
+        SqlNodeList.EMPTY, SqlHint.HintOptionFormat.EMPTY);
   }
 
   /** Visits a Union; called by {@link #dispatch} via reflection. */
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java 
b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index 9ea2f80..1af0b05 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -69,6 +69,7 @@ import org.apache.calcite.sql.SqlOverOperator;
 import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSetOperator;
+import org.apache.calcite.sql.SqlTableRef;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.sql.fun.SqlCase;
@@ -505,6 +506,7 @@ public abstract class SqlImplementor {
     assert node instanceof SqlJoin
         || node instanceof SqlIdentifier
         || node instanceof SqlMatchRecognize
+        || node instanceof SqlTableRef
         || node instanceof SqlCall
             && (((SqlCall) node).getOperator() instanceof SqlSetOperator
                 || ((SqlCall) node).getOperator() == SqlStdOperatorTable.AS
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java 
b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
index 0f6f3fe..0942973 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
@@ -541,6 +541,11 @@ public class SqlDialect {
         RelDataTypeSystem.DEFAULT);
   }
 
+  /** Converts table scan hints. The default implementation suppresses all 
hints. */
+  public void unparseTableScanHints(SqlWriter writer,
+      SqlNodeList hints, int leftPrec, int rightPrec) {
+  }
+
   /**
    * Returns whether the string contains any characters outside the
    * comfortable 7-bit ASCII range (32 through 127, plus linefeed (10) and
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlTableRef.java 
b/core/src/main/java/org/apache/calcite/sql/SqlTableRef.java
index 3645fb3..51907f2 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlTableRef.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlTableRef.java
@@ -73,10 +73,7 @@ public class SqlTableRef extends SqlCall {
   @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) 
{
     tableName.unparse(writer, leftPrec, rightPrec);
     if (this.hints != null && this.hints.size() > 0) {
-      writer.newlineAndIndent();
-      writer.keyword("/*+");
-      this.hints.unparse(writer, 0, 0);
-      writer.keyword("*/");
+      writer.getDialect().unparseTableScanHints(writer, this.hints, leftPrec, 
rightPrec);
     }
   }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/dialect/AnsiSqlDialect.java 
b/core/src/main/java/org/apache/calcite/sql/dialect/AnsiSqlDialect.java
index e9621de..76501b4 100644
--- a/core/src/main/java/org/apache/calcite/sql/dialect/AnsiSqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/AnsiSqlDialect.java
@@ -17,6 +17,8 @@
 package org.apache.calcite.sql.dialect;
 
 import org.apache.calcite.sql.SqlDialect;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlWriter;
 
 /**
  * A <code>SqlDialect</code> implementation for an unknown ANSI compatible 
database.
@@ -38,4 +40,14 @@ public class AnsiSqlDialect extends SqlDialect {
   public AnsiSqlDialect(Context context) {
     super(context);
   }
+
+  /** Converts table scan hints.*/
+  @Override public void unparseTableScanHints(SqlWriter writer,
+      SqlNodeList hints, int leftPrec, int rightPrec) {
+    writer.newlineAndIndent();
+    writer.keyword("/*+");
+    hints.unparse(writer, 0, 0);
+    writer.keyword("*/");
+  }
+
 }
diff --git 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 6851e82..020ac74 100644
--- 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -24,6 +24,9 @@ import org.apache.calcite.plan.hep.HepPlanner;
 import org.apache.calcite.plan.hep.HepProgramBuilder;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.hint.HintPredicates;
+import org.apache.calcite.rel.hint.HintStrategyTable;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.rules.AggregateJoinTransposeRule;
@@ -46,6 +49,7 @@ import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.SqlWriterConfig;
+import org.apache.calcite.sql.dialect.AnsiSqlDialect;
 import org.apache.calcite.sql.dialect.CalciteSqlDialect;
 import org.apache.calcite.sql.dialect.HiveSqlDialect;
 import org.apache.calcite.sql.dialect.JethroDataSqlDialect;
@@ -4910,6 +4914,33 @@ class RelToSqlConverterTest {
         isLinux(expectedSql2));
   }
 
+  @Test void testTableScanHints() {
+    final RelBuilder builder = relBuilder();
+    builder.getCluster().setHintStrategies(HintStrategyTable.builder()
+        .hintStrategy("PLACEHOLDERS", HintPredicates.TABLE_SCAN)
+        .build());
+    final RelNode root = builder
+        .scan("orders")
+        .hints(RelHint.builder("PLACEHOLDERS")
+            .hintOption("a", "b")
+            .build())
+        .project(builder.field("PRODUCT"))
+        .build();
+
+    final String expectedSql = "SELECT \"PRODUCT\"\n"
+        + "FROM \"scott\".\"orders\"";
+    assertThat(
+        toSql(root, DatabaseProduct.CALCITE.getDialect()),
+        isLinux(expectedSql));
+    final String expectedSql2 = "SELECT PRODUCT\n"
+        + "FROM scott.orders\n"
+        + "/*+ PLACEHOLDERS(a = 'b') */";
+    assertThat(
+        toSql(root, new AnsiSqlDialect(SqlDialect.EMPTY_CONTEXT)),
+        isLinux(expectedSql2));
+  }
+
+
   @Test void testSelectWithoutFromEmulationForHiveAndBigQuery() {
     String query = "select 2 + 2";
     final String expected = "SELECT 2 + 2";

Reply via email to