Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-4640] Propagate table scan hints to JDBC #2426

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -170,7 +170,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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!hints.isEmpty()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this

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 @@ -538,6 +538,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 @@ -47,6 +50,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 @@ -4894,6 +4898,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