This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 cd2b705856 [CALCITE-7743] RelJson cannot emit hints
cd2b705856 is described below
commit cd2b7058566777be0f1d6a1083b796dedb99c156
Author: microbluey <[email protected]>
AuthorDate: Tue Aug 25 18:06:25 2026 +0800
[CALCITE-7743] RelJson cannot emit hints
Serialize RelHint in RelJson and read the hints entry back in
TableScan(RelInput) via a new RelInput.getHints(), so hints survive
a JSON dump/read round trip instead of crashing the serializer.
---
.../main/java/org/apache/calcite/rel/RelInput.java | 9 ++++
.../org/apache/calcite/rel/core/TableScan.java | 2 +-
.../apache/calcite/rel/externalize/RelJson.java | 61 ++++++++++++++++++++++
.../calcite/rel/externalize/RelJsonReader.java | 5 ++
.../calcite/rel/externalize/RelJsonTest.java | 60 +++++++++++++++++++++
5 files changed, 136 insertions(+), 1 deletion(-)
diff --git a/core/src/main/java/org/apache/calcite/rel/RelInput.java
b/core/src/main/java/org/apache/calcite/rel/RelInput.java
index c3d8e41b57..fd1bbc213a 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelInput.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelInput.java
@@ -20,6 +20,7 @@
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
@@ -62,6 +63,14 @@ public interface RelInput {
List<AggregateCall> getAggregateCalls(String tag);
+ /**
+ * Returns the hints attached to this relational expression, or an empty
+ * list if there are none.
+ */
+ default List<RelHint> getHints() {
+ return ImmutableList.of();
+ }
+
@Nullable Object get(String tag);
/**
diff --git a/core/src/main/java/org/apache/calcite/rel/core/TableScan.java
b/core/src/main/java/org/apache/calcite/rel/core/TableScan.java
index 0d6c4a9cdd..00bc6048fe 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/TableScan.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/TableScan.java
@@ -88,7 +88,7 @@ protected TableScan(RelOptCluster cluster, RelTraitSet
traitSet,
* Creates a TableScan by parsing serialized output.
*/
protected TableScan(RelInput input) {
- this(input.getCluster(), input.getTraitSet(), ImmutableList.of(),
input.getTable("table"));
+ this(input.getCluster(), input.getTraitSet(), input.getHints(),
input.getTable("table"));
}
//~ Methods ----------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
b/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
index 8fa5abd149..5c66fa5ded 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
@@ -34,6 +34,7 @@
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
@@ -499,6 +500,8 @@ public Object toJson(AggregateCall node) {
return toJson((RelDataTypeField) value);
} else if (value instanceof RelDistribution) {
return toJson((RelDistribution) value);
+ } else if (value instanceof RelHint) {
+ return toJson((RelHint) value);
} else if (value instanceof Sarg) {
//noinspection unchecked,rawtypes
return toJson((Sarg) value);
@@ -518,6 +521,64 @@ public Object toJson(AggregateCall node) {
}
}
+ /** Serializes a {@link RelHint} as a JSON map. */
+ public Object toJson(RelHint node) {
+ final Map<String, @Nullable Object> map = jsonBuilder().map();
+ map.put("name", node.hintName);
+ map.put("inheritPath", node.inheritPath);
+ if (!node.listOptions.isEmpty()) {
+ map.put("options", node.listOptions);
+ }
+ if (!node.kvOptions.isEmpty()) {
+ map.put("kvOptions", node.kvOptions);
+ }
+ if (node.pos != SqlParserPos.ZERO) {
+ map.put("pos", toJson(node.pos));
+ }
+ return map;
+ }
+
+ /** Converts a JSON object, such as produced by {@link #toJson(RelHint)},
+ * into a {@link RelHint}. */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public RelHint toHint(Map<String, @Nullable Object> map) {
+ final RelHint.Builder builder = RelHint.builder(get(map, "name"));
+ final List<Integer> inheritPath = get(map, "inheritPath");
+ if (!inheritPath.isEmpty()) {
+ builder.inheritPath(inheritPath);
+ }
+ final List<String> options = (List<String>) map.get("options");
+ if (options != null) {
+ builder.hintOptions(options);
+ }
+ final Map<String, String> kvOptions = (Map<String, String>)
map.get("kvOptions");
+ if (kvOptions != null) {
+ builder.hintOptions(kvOptions);
+ }
+ final Map<String, Object> pos = (Map<String, Object>) map.get("pos");
+ if (pos != null) {
+ builder.position(
+ new SqlParserPos(get(pos, "line"), get(pos, "column"),
+ get(pos, "end_line"), get(pos, "end_column")));
+ }
+ return builder.build();
+ }
+
+ /** Converts a JSON list, such as produced by {@link #toJson(RelHint)},
+ * into a list of {@link RelHint}s. Returns an empty list if {@code o} is
+ * null. */
+ @SuppressWarnings("unchecked")
+ public List<RelHint> toHints(@Nullable Object o) {
+ if (o == null) {
+ return ImmutableList.of();
+ }
+ final List<RelHint> hints = new ArrayList<>();
+ for (Object hint : (List) o) {
+ hints.add(toHint((Map<String, @Nullable Object>) hint));
+ }
+ return hints;
+ }
+
public <C extends Comparable<C>> Object toJson(Sarg<C> node) {
final Map<String, @Nullable Object> map = jsonBuilder().map();
map.put("rangeSet", toJson(node.rangeSet));
diff --git
a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
index 2cdd785fe8..55886200a5 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
@@ -27,6 +27,7 @@
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexLiteral;
@@ -167,6 +168,10 @@ private void readRel(final Map<String, Object> jsonRel) {
return inputs.build();
}
+ @Override public List<RelHint> getHints() {
+ return relJson.toHints(jsonRel.get("hints"));
+ }
+
@Override public @Nullable RexNode getExpression(String tag) {
return relJson.toRex(this, jsonRel.get(tag));
}
diff --git
a/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
b/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
index 034365aede..6800a97d0d 100644
--- a/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
@@ -16,8 +16,13 @@
*/
package org.apache.calcite.rel.externalize;
+import org.apache.calcite.adapter.java.ReflectiveSchema;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.TableScan;
+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.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
@@ -34,7 +39,9 @@
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.test.DiffRepository;
+import org.apache.calcite.test.schemata.hr.HrSchema;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Planner;
@@ -44,8 +51,11 @@
import org.junit.jupiter.api.Test;
+import java.io.IOException;
import java.util.EnumSet;
+import java.util.List;
+import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@@ -174,4 +184,54 @@ plan, containsString("\"exprs\": [\n"
+ " \"syntax\": \"INTERNAL\"\n"
+ " },"));
}
+
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7743">[CALCITE-7743]
+ * RelJson cannot emit hints</a>. */
+ @Test void testHint() {
+ final String sql = "select *\n"
+ + "from \"emps\" /*+ index(name) */";
+ final String plan =
+ Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
+ final SchemaPlus schema =
+ rootSchema.add("hr", new ReflectiveSchema(new HrSchema()));
+ final FrameworkConfig config = Frameworks.newConfigBuilder()
+ .parserConfig(SqlParser.Config.DEFAULT)
+ .defaultSchema(schema)
+ .sqlToRelConverterConfig(SqlToRelConverter.config()
+ .withHintStrategyTable(HintStrategyTable.builder()
+ .hintStrategy("index", HintPredicates.TABLE_SCAN)
+ .build()))
+ .build();
+ try {
+ final Planner planner = Frameworks.getPlanner(config);
+ final SqlNode n = planner.validate(planner.parse(sql));
+ final RelNode root = planner.rel(n).project();
+ final String json =
+ RelOptUtil.dumpPlan("", root,
+ SqlExplainFormat.JSON, SqlExplainLevel.DIGEST_ATTRIBUTES);
+
+ // Reads the plan back and verifies that the hints survive.
+ final RelJsonReader reader =
+ new RelJsonReader(cluster, relOptSchema, schema);
+ final RelNode root2 = reader.read(json);
+ final List<RelHint> expectedHints = tableScan(root).getHints();
+ assertThat(tableScan(root2).getHints(), is(expectedHints));
+ return json;
+ } catch (SqlParseException | ValidationException
+ | RelConversionException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ assertThat(plan, containsString("\"name\": \"INDEX\""));
+ assertThat(plan, containsString("\"options\": ["));
+ assertThat(plan, containsString("\"NAME\""));
+ }
+
+ /** Returns the {@link TableScan} of this plan. */
+ private static TableScan tableScan(RelNode rel) {
+ if (rel instanceof TableScan) {
+ return (TableScan) rel;
+ }
+ return (TableScan) rel.getInput(0);
+ }
}