Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,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
Expand Down Expand Up @@ -610,7 +621,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:
Expand Down Expand Up @@ -876,6 +887,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);
}
Expand Down
101 changes: 101 additions & 0 deletions core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
Loading