This is an automated email from the ASF dual-hosted git repository.
rubenada pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 7173989162 [CALCITE-7905] AnsiSqlDialect does not check hint names and
options for block-comment delimiters
7173989162 is described below
commit 71739891623e613f64898caabb50665ecdda33f2
Author: Ruben Quesada Lopez <[email protected]>
AuthorDate: Thu Oct 29 17:24:52 2026 +0000
[CALCITE-7905] AnsiSqlDialect does not check hint names and options for
block-comment delimiters
---
.../calcite/rel/rel2sql/RelToSqlConverter.java | 4 +--
.../apache/calcite/sql/dialect/AnsiSqlDialect.java | 20 +++++++++++++
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 33 ++++++++++++++++++++++
3 files changed, 55 insertions(+), 2 deletions(-)
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 7806ab5e75..32d1a6c0f9 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
@@ -1086,7 +1086,7 @@ public Result visit(TableScan e) {
}
private static SqlHint toSqlHint(RelHint hint, SqlParserPos pos) {
- if (hint.kvOptions != null) {
+ if (!hint.kvOptions.isEmpty()) {
return new SqlHint(pos, new SqlIdentifier(hint.hintName, pos),
SqlNodeList.of(pos, hint.kvOptions.entrySet().stream()
.flatMap(
@@ -1094,7 +1094,7 @@ private static SqlHint toSqlHint(RelHint hint,
SqlParserPos pos) {
SqlLiteral.createCharString(e.getValue(), pos)))
.collect(Collectors.toList())),
SqlHint.HintOptionFormat.KV_LIST);
- } else if (hint.listOptions != null) {
+ } else if (!hint.listOptions.isEmpty()) {
return new SqlHint(pos, new SqlIdentifier(hint.hintName, pos),
SqlNodeList.of(pos, hint.listOptions.stream()
.map(e -> SqlLiteral.createCharString(e, pos))
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 76501b451a..cc6d752720 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,9 +17,13 @@
package org.apache.calcite.sql.dialect;
import org.apache.calcite.sql.SqlDialect;
+import org.apache.calcite.sql.SqlHint;
+import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlWriter;
+import static com.google.common.base.Preconditions.checkArgument;
+
/**
* A <code>SqlDialect</code> implementation for an unknown ANSI compatible
database.
*/
@@ -44,10 +48,26 @@ public AnsiSqlDialect(Context context) {
/** Converts table scan hints.*/
@Override public void unparseTableScanHints(SqlWriter writer,
SqlNodeList hints, int leftPrec, int rightPrec) {
+ for (SqlNode node : hints) {
+ final SqlHint hint = (SqlHint) node;
+ checkHintText(hint.getName());
+ hint.getOptionList().forEach(AnsiSqlDialect::checkHintText);
+ hint.getOptionKVPairs().forEach((k, v) -> {
+ checkHintText(k);
+ checkHintText(v);
+ });
+ }
writer.newlineAndIndent();
writer.keyword("/*+");
hints.unparse(writer, 0, 0);
writer.keyword("*/");
}
+ /** Throws if a hint name or option contains a block-comment delimiter;
+ * hints are written inside a block comment, which has no escape syntax. */
+ private static void checkHintText(String s) {
+ checkArgument(!s.contains("*/") && !s.contains("/*"),
+ "Hint name or option contains a block-comment delimiter: %s", s);
+ }
+
}
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 ee7f9d5803..7d716d0947 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
@@ -8785,6 +8785,39 @@ private void checkLiteral2(String expression, String
expected) {
isLinux(expectedSql2));
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7905">[CALCITE-7905]
+ * AnsiSqlDialect does not check hint names and options for block-comment
delimiters</a>. */
+ @Test void testTableScanHintsWithCommentDelimiter() {
+ final RelBuilder builder = relBuilder();
+ builder.getCluster().setHintStrategies(HintStrategyTable.builder()
+ .hintStrategy("PLACEHOLDER", HintPredicates.TABLE_SCAN)
+ .build());
+ // Each entry maps the text expected in the error message to a hint that
contains it
+ final Map<String, RelHint> hints = ImmutableMap.<String, RelHint>builder()
+ // hint name, no options
+ .put("PLACE*/HOLDER", RelHint.builder("PLACE*/HOLDER").build())
+ // key-value options, in the value and in the key
+ .put("f */ g", RelHint.builder("PLACEHOLDER").hintOption("a", "f */
g").build())
+ .put("h /* i", RelHint.builder("PLACEHOLDER").hintOption("h /* i",
"b").build())
+ // list options
+ .put("b */ c", RelHint.builder("PLACEHOLDER").hintOption("b */
c").build())
+ .put("d /* e",
RelHint.builder("PLACEHOLDER").hintOption("a").hintOption("d /* e").build())
+ .build();
+ for (Map.Entry<String, RelHint> entry : hints.entrySet()) {
+ final RelNode root = builder
+ .scan("orders")
+ .hints(entry.getValue())
+ .project(builder.field("PRODUCT"))
+ .build();
+
+ final IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, () ->
+ toSql(root, new AnsiSqlDialect(SqlDialect.EMPTY_CONTEXT)));
+ assertThat(e.getMessage(), containsString(entry.getKey()));
+ }
+ }
+
/** Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-2118">[CALCITE-2118]
* RelToSqlConverter should only generate "*" if field names match</a>. */