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();

Reply via email to