Skip to content

Commit

Permalink
[CALCITE-4640] Propagate table scan hints to JDBC
Browse files Browse the repository at this point in the history
close #2426
  • Loading branch information
kramerul authored and danny0405 committed Jul 7, 2021
1 parent d46137a commit 09a8a2f
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 18 deletions.
Expand Up @@ -22,8 +22,6 @@
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.util.Util;

import com.google.common.collect.ImmutableList;

/**
* State for generating a SQL statement.
*/
Expand All @@ -33,14 +31,6 @@ public JdbcImplementor(SqlDialect dialect, JavaTypeFactory typeFactory) {
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);
}
Expand Down
Expand Up @@ -175,7 +175,7 @@ public SqlIdentifier tableName() {

@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);
}

Expand Down
Expand Up @@ -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;

Expand All @@ -29,6 +31,8 @@

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.
*/
Expand All @@ -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);
}
}
Expand Up @@ -44,6 +44,7 @@
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;
Expand All @@ -61,6 +62,7 @@
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;
Expand All @@ -71,6 +73,7 @@
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;
Expand Down Expand Up @@ -106,6 +109,7 @@
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;

Expand Down Expand Up @@ -525,7 +529,37 @@ private static SqlNode groupItem(List<SqlNode> groupKeys,
/** 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. */
Expand Down
Expand Up @@ -69,6 +69,7 @@
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;
Expand Down Expand Up @@ -505,6 +506,7 @@ SqlSelect wrapSelect(SqlNode node) {
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
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlDialect.java
Expand Up @@ -541,6 +541,11 @@ public void unparseSqlIntervalLiteral(SqlWriter writer,
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
Expand Down
5 changes: 1 addition & 4 deletions core/src/main/java/org/apache/calcite/sql/SqlTableRef.java
Expand Up @@ -73,10 +73,7 @@ public SqlTableRef(SqlParserPos pos, SqlIdentifier tableName, SqlNodeList hints)
@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);
}
}
}
Expand Up @@ -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.
Expand All @@ -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("*/");
}

}
Expand Up @@ -24,6 +24,9 @@
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;
Expand All @@ -46,6 +49,7 @@
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;
Expand Down Expand Up @@ -4910,6 +4914,33 @@ private void checkLiteral2(String expression, String expected) {
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";
Expand Down

0 comments on commit 09a8a2f

Please sign in to comment.