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 5e694d2546 [CALCITE-6792] RelJsonReader failed to read RelJsonWriter
output if 'NaN' or 'Infinity' approximate numerics are present
5e694d2546 is described below
commit 5e694d25463965bdce9a2ec4bcc813dec81d5984
Author: 1fanwang <[email protected]>
AuthorDate: Wed Aug 26 01:03:00 2026 -0400
[CALCITE-6792] RelJsonReader failed to read RelJsonWriter output if 'NaN'
or 'Infinity' approximate numerics are present
Signed-off-by: 1fanwang <[email protected]>
---
.../apache/calcite/rel/externalize/RelJson.java | 18 +++-
.../org/apache/calcite/plan/RelWriterTest.java | 101 +++++++++++++++++++++
2 files changed, 118 insertions(+), 1 deletion(-)
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 4c47939ea6..4f9aa5f14a 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
@@ -460,6 +460,17 @@ public Object toJson(AggregateCall node) {
return map;
}
+ /** Converts a literal's value to JSON. A non-finite approximate value
becomes
+ * a string, because JSON has no syntax for NaN or infinity and the
+ * {@link java.math.BigDecimal} that the reader parses numbers into cannot
+ * represent one either. */
+ private @Nullable Object toJsonLiteralValue(@Nullable Object value) {
+ if (value instanceof Double && !Double.isFinite((Double) value)) {
+ return value.toString();
+ }
+ return toJson(value);
+ }
+
public @Nullable Object toJson(@Nullable Object value) {
if (value == null
|| value instanceof Number
@@ -671,7 +682,7 @@ public Object toJson(RexNode node) {
map.put("literal",
value instanceof Enum
? RelEnumTypes.fromEnum((Enum) value)
- : toJson(value));
+ : toJsonLiteralValue(value));
map.put("type", toJson(node.getType()));
return map;
case INPUT_REF:
@@ -937,6 +948,11 @@ public RexNode toRex(RelOptCluster cluster, Object o) {
literal = ByteString.of((String) literal, 16);
} else if (sqlTypeName == SqlTypeName.UUID) {
literal = SqlFunctions.stringToUuid((String) literal);
+ } else if (literal instanceof String
+ && SqlTypeName.APPROX_TYPES.contains(sqlTypeName)) {
+ // A non-finite value that toJson wrote as a string because JSON
+ // cannot represent it as a number.
+ literal = Double.valueOf((String) literal);
}
return rexBuilder.makeLiteral(literal, type);
}
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index a107afd17f..0fe768c242 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -648,6 +648,91 @@ private static Fixture relFn(Function<RelBuilder, RelNode>
relFn) {
.assertThatJson(isLinux(UUID_LITERAL));
}
+ static final String NON_FINITE_APPROX_LITERAL = "{\n"
+ + " \"rels\": [\n"
+ + " {\n"
+ + " \"id\": \"0\",\n"
+ + " \"relOp\": \"LogicalTableScan\",\n"
+ + " \"table\": [\n"
+ + " \"scott\",\n"
+ + " \"EMP\"\n"
+ + " ],\n"
+ + " \"inputs\": []\n"
+ + " },\n"
+ + " {\n"
+ + " \"id\": \"1\",\n"
+ + " \"relOp\": \"LogicalProject\",\n"
+ + " \"fields\": [\n"
+ + " \"$f0\",\n"
+ + " \"$f1\",\n"
+ + " \"$f2\",\n"
+ + " \"$f3\",\n"
+ + " \"$f4\"\n"
+ + " ],\n"
+ + " \"exprs\": [\n"
+ + " {\n"
+ + " \"literal\": \"NaN\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"DOUBLE\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": \"Infinity\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"DOUBLE\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": \"-Infinity\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"DOUBLE\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": 1.5,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"DOUBLE\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": \"Infinity\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"CHAR\",\n"
+ + " \"nullable\": false,\n"
+ + " \"precision\": 8\n"
+ + " }\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6792">[CALCITE-6792]
+ * RelJsonReader failed to read RelJsonWriter output if 'NaN' or 'Infinity'
+ * approximate numerics are present</a>. */
+ @Test void testNonFiniteApproxLiteral() {
+ final Function<RelBuilder, RelNode> relFn = b -> {
+ final RexBuilder rexBuilder = b.getRexBuilder();
+ final RelDataType doubleType =
+ b.getTypeFactory().createSqlType(SqlTypeName.DOUBLE);
+ return b.scan("EMP")
+ .project(rexBuilder.makeApproxLiteral(Double.NaN, doubleType),
+ rexBuilder.makeApproxLiteral(Double.POSITIVE_INFINITY,
doubleType),
+ rexBuilder.makeApproxLiteral(Double.NEGATIVE_INFINITY,
doubleType),
+ rexBuilder.makeApproxLiteral(1.5d, doubleType),
+ b.literal("Infinity"))
+ .build();
+ };
+ relFn(relFn)
+ .assertThatJson(isLinux(NON_FINITE_APPROX_LITERAL))
+ .assertThatPlanRoundTrips();
+ }
+
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonWriter} on
* a simple tree of relational expressions, consisting of a table, a filter
@@ -1931,6 +2016,22 @@ Fixture assertThatJson(Matcher<String> matcher) {
return this;
}
+ @SuppressWarnings("UnusedReturnValue")
+ Fixture assertThatPlanRoundTrips() {
+ final FrameworkConfig config = RelBuilderTest.config().build();
+ final RelBuilder b = RelBuilder.create(config);
+ final RelNode rel = relFn.apply(b);
+ final String relJson =
+ RelOptUtil.dumpPlan("", rel, SqlExplainFormat.JSON,
+ SqlExplainLevel.EXPPLAN_ATTRIBUTES);
+ final String originalPlan =
+ RelOptUtil.dumpPlan("", rel, format,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
+ final String roundTrippedPlan =
+ deserializeAndDump(getSchema(rel), relJson, format);
+ assertThat(roundTrippedPlan, is(originalPlan));
+ return this;
+ }
+
@SuppressWarnings("UnusedReturnValue")
Fixture assertThatPlan(Matcher<String> matcher) {
final FrameworkConfig config = RelBuilderTest.config().build();