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";