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 c3f8a7c2caa5..e55e26ddaf3b 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 @@ -554,23 +554,16 @@ private Object toJson(RexNode node) { final RexCall call = (RexCall) node; map = jsonBuilder().map(); map.put("op", toJson(call.getOperator())); + map.put("type", toJson(call.getType())); final List<@Nullable Object> list = jsonBuilder().list(); for (RexNode operand : call.getOperands()) { list.add(toJson(operand)); } map.put("operands", list); - switch (node.getKind()) { - case CAST: - map.put("type", toJson(node.getType())); - break; - default: - break; - } if (call.getOperator() instanceof SqlFunction) { if (((SqlFunction) call.getOperator()).getFunctionType().isUserDefined()) { SqlOperator op = call.getOperator(); map.put("class", op.getClass().getName()); - map.put("type", toJson(node.getType())); map.put("deterministic", op.isDeterministic()); map.put("dynamic", op.isDynamicFunction()); } @@ -578,7 +571,6 @@ private Object toJson(RexNode node) { if (call instanceof RexOver) { RexOver over = (RexOver) call; map.put("distinct", over.isDistinct()); - map.put("type", toJson(node.getType())); map.put("window", toJson(over.getWindow())); } return map; 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 6c5275ec3fa1..deaec7db77fd 100644 --- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java +++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java @@ -129,6 +129,10 @@ class RelWriterTest { + " \"kind\": \"EQUALS\",\n" + " \"syntax\": \"BINARY\"\n" + " },\n" + + " \"type\": {\n" + + " \"type\": \"BOOLEAN\",\n" + + " \"nullable\": false\n" + + " },\n" + " \"operands\": [\n" + " {\n" + " \"input\": 1,\n" @@ -288,6 +292,10 @@ class RelWriterTest { + " \"kind\": \"COUNT\",\n" + " \"syntax\": \"FUNCTION_STAR\"\n" + " },\n" + + " \"type\": {\n" + + " \"type\": \"BIGINT\",\n" + + " \"nullable\": false\n" + + " },\n" + " \"operands\": [\n" + " {\n" + " \"input\": 0,\n" @@ -295,10 +303,6 @@ class RelWriterTest { + " }\n" + " ],\n" + " \"distinct\": false,\n" - + " \"type\": {\n" - + " \"type\": \"BIGINT\",\n" - + " \"nullable\": false\n" - + " },\n" + " \"window\": {\n" + " \"partition\": [\n" + " {\n" @@ -330,6 +334,10 @@ class RelWriterTest { + " \"kind\": \"SUM\",\n" + " \"syntax\": \"FUNCTION\"\n" + " },\n" + + " \"type\": {\n" + + " \"type\": \"BIGINT\",\n" + + " \"nullable\": false\n" + + " },\n" + " \"operands\": [\n" + " {\n" + " \"input\": 0,\n" @@ -337,10 +345,6 @@ class RelWriterTest { + " }\n" + " ],\n" + " \"distinct\": false,\n" - + " \"type\": {\n" - + " \"type\": \"BIGINT\",\n" - + " \"nullable\": false\n" - + " },\n" + " \"window\": {\n" + " \"partition\": [\n" + " {\n" @@ -779,7 +783,8 @@ private void assertThatReadExpressionResult(String json, Matcher matcher throw TestUtil.rethrow(e); } final RelJson relJson = RelJson.create() - .withInputTranslator(RelWriterTest::translateInput); + .withInputTranslator(RelWriterTest::translateInput) + .withLibraryOperatorTable(); final RexNode e = relJson.toRex(cluster, o); assertThat(e.toString(), is(matcher)); } @@ -945,6 +950,42 @@ void testAggregateWithAlias(SqlExplainFormat format) { assertThat(result, isLinux(expected)); } + /** Test case for + * [CALCITE-5607] + *

Before the fix, RelJson.toRex would throw an ArrayIndexOutOfBounds error + * when deserialization. + * */ + @Test void testDeserializeMinusDateOperator() { + final FrameworkConfig config = RelBuilderTest.config().build(); + final RelBuilder builder = RelBuilder.create(config); + final RexBuilder rb = builder.getRexBuilder(); + final RelDataTypeFactory typeFactory = rb.getTypeFactory(); + final SqlIntervalQualifier qualifier = + new SqlIntervalQualifier(TimeUnit.MONTH, null, SqlParserPos.ZERO); + final RexNode op1 = rb.makeTimestampLiteral(new TimestampString("2012-12-03 12:34:44"), 0); + final RexNode op2 = rb.makeTimestampLiteral(new TimestampString("2014-12-23 12:34:44"), 0); + final RelDataType intervalType = + typeFactory.createTypeWithNullability( + typeFactory.createSqlIntervalType(qualifier), + op1.getType().isNullable() || op2.getType().isNullable()); + final RelNode rel = builder + .scan("EMP") + .project(builder.field("JOB"), + rb.makeCall(intervalType, + SqlStdOperatorTable.MINUS_DATE, + ImmutableList.of(op2, op1)) + ).build(); + final RelJsonWriter jsonWriter = + new RelJsonWriter(new JsonBuilder(), RelJson::withLibraryOperatorTable); + rel.explain(jsonWriter); + String relJson = jsonWriter.asString(); + String result = deserializeAndDumpToTextFormat(getSchema(rel), relJson); + final String expected = + "LogicalProject(JOB=[$2], $f1=[-(2014-12-23 12:34:44, 2012-12-03 12:34:44)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + assertThat(result, isLinux(expected)); + } + @Test void testAggregateWithoutAlias() { final FrameworkConfig config = RelBuilderTest.config().build(); final RelBuilder builder = RelBuilder.create(config);