This is an automated email from the ASF dual-hosted git repository.
jcamacho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new e94c157 [CALCITE-2858] Improvements in JSON writer and reader for
plans
e94c157 is described below
commit e94c1574e234ae58b7daac327ea8ce69d2873f22
Author: Jesus Camacho Rodriguez <[email protected]>
AuthorDate: Wed Feb 20 14:55:51 2019 -0800
[CALCITE-2858] Improvements in JSON writer and reader for plans
Close apache/calcite#1056
---
.../apache/calcite/rel/externalize/RelJson.java | 260 +++++++++++++++---
.../calcite/rel/externalize/RelJsonReader.java | 11 +-
.../calcite/rel/externalize/RelJsonWriter.java | 6 +-
.../java/org/apache/calcite/sql/SqlWindow.java | 4 +-
.../org/apache/calcite/plan/RelWriterTest.java | 302 ++++++++++++++++++++-
.../java/org/apache/calcite/test/JdbcTest.java | 49 +++-
6 files changed, 574 insertions(+), 58 deletions(-)
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 422be42..92bdbda 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
@@ -24,6 +24,8 @@ import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelDistribution;
import org.apache.calcite.rel.RelDistributions;
import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelFieldCollation.Direction;
+import org.apache.calcite.rel.RelFieldCollation.NullDirection;
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.AggregateCall;
@@ -35,13 +37,20 @@ import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexFieldAccess;
+import org.apache.calcite.rex.RexFieldCollation;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexSlot;
+import org.apache.calcite.rex.RexWindow;
+import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlWindow;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.JsonBuilder;
@@ -54,8 +63,10 @@ import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Utilities for converting {@link org.apache.calcite.rel.RelNode}
@@ -187,22 +198,33 @@ public class RelJson {
builder.add((String) jsonMap.get("name"), toType(typeFactory,
jsonMap));
}
return builder.build();
- } else {
+ } else if (o instanceof Map) {
+ @SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>) o;
- final SqlTypeName sqlTypeName =
- Util.enumVal(SqlTypeName.class, (String) map.get("type"));
- final Integer precision = (Integer) map.get("precision");
- final Integer scale = (Integer) map.get("scale");
- final RelDataType type;
- if (precision == null) {
- type = typeFactory.createSqlType(sqlTypeName);
- } else if (scale == null) {
- type = typeFactory.createSqlType(sqlTypeName, precision);
+ final Object fields = map.get("fields");
+ if (fields != null) {
+ // Nested struct
+ return toType(typeFactory, fields);
} else {
- type = typeFactory.createSqlType(sqlTypeName, precision, scale);
+ final SqlTypeName sqlTypeName =
+ Util.enumVal(SqlTypeName.class, (String) map.get("type"));
+ final Integer precision = (Integer) map.get("precision");
+ final Integer scale = (Integer) map.get("scale");
+ final RelDataType type;
+ if (precision == null) {
+ type = typeFactory.createSqlType(sqlTypeName);
+ } else if (scale == null) {
+ type = typeFactory.createSqlType(sqlTypeName, precision);
+ } else {
+ type = typeFactory.createSqlType(sqlTypeName, precision, scale);
+ }
+ final boolean nullable = (Boolean) map.get("nullable");
+ return typeFactory.createTypeWithNullability(type, nullable);
}
- final boolean nullable = (Boolean) map.get("nullable");
- return typeFactory.createTypeWithNullability(type, nullable);
+ } else {
+ final SqlTypeName sqlTypeName =
+ Util.enumVal(SqlTypeName.class, (String) o);
+ return typeFactory.createSqlType(sqlTypeName);
}
}
@@ -215,7 +237,7 @@ public class RelJson {
return map;
}
- Object toJson(Object value) {
+ public Object toJson(Object value) {
if (value == null
|| value instanceof Number
|| value instanceof String
@@ -223,6 +245,12 @@ public class RelJson {
return value;
} else if (value instanceof RexNode) {
return toJson((RexNode) value);
+ } else if (value instanceof RexWindow) {
+ return toJson((RexWindow) value);
+ } else if (value instanceof RexFieldCollation) {
+ return toJson((RexFieldCollation) value);
+ } else if (value instanceof RexWindowBound) {
+ return toJson((RexWindowBound) value);
} else if (value instanceof CorrelationId) {
return toJson((CorrelationId) value);
} else if (value instanceof List) {
@@ -273,8 +301,13 @@ public class RelJson {
}
private Object toJson(RelDataTypeField node) {
- final Map<String, Object> map =
- (Map<String, Object>) toJson(node.getType());
+ final Map<String, Object> map;
+ if (node.getType().isStruct()) {
+ map = jsonBuilder.map();
+ map.put("fields", toJson(node.getType()));
+ } else {
+ map = (Map<String, Object>) toJson(node.getType());
+ }
map.put("name", node.getName());
return map;
}
@@ -294,17 +327,11 @@ public class RelJson {
return map;
case LITERAL:
final RexLiteral literal = (RexLiteral) node;
- final Object value2 = literal.getValue2();
- if (value2 == null) {
- // Special treatment for null literal because (1) we wouldn't want
- // 'null' to be confused as an empty expression and (2) for null
- // literals we need an explicit type.
- map = jsonBuilder.map();
- map.put("literal", null);
- map.put("type", literal.getTypeName().name());
- return map;
- }
- return value2;
+ final Object value = literal.getValue3();
+ map = jsonBuilder.map();
+ map.put("literal", value);
+ map.put("type", toJson(node.getType()));
+ return map;
case INPUT_REF:
case LOCAL_REF:
map = jsonBuilder.map();
@@ -332,15 +359,74 @@ public class RelJson {
}
if (call.getOperator() instanceof SqlFunction) {
if (((SqlFunction)
call.getOperator()).getFunctionType().isUserDefined()) {
- map.put("class", call.getOperator().getClass().getName());
+ 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());
}
}
+ 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;
}
throw new UnsupportedOperationException("unknown rex " + node);
}
}
+ private Object toJson(RexWindow window) {
+ final Map<String, Object> map = jsonBuilder.map();
+ if (window.partitionKeys.size() > 0) {
+ map.put("partition", toJson(window.partitionKeys));
+ }
+ if (window.orderKeys.size() > 0) {
+ map.put("order", toJson(window.orderKeys));
+ }
+ if (window.getLowerBound() == null) {
+ // No ROWS or RANGE clause
+ } else if (window.getUpperBound() == null) {
+ if (window.isRows()) {
+ map.put("rows-lower", toJson(window.getLowerBound()));
+ } else {
+ map.put("range-lower", toJson(window.getLowerBound()));
+ }
+ } else {
+ if (window.isRows()) {
+ map.put("rows-lower", toJson(window.getLowerBound()));
+ map.put("rows-upper", toJson(window.getUpperBound()));
+ } else {
+ map.put("range-lower", toJson(window.getLowerBound()));
+ map.put("range-upper", toJson(window.getUpperBound()));
+ }
+ }
+ return map;
+ }
+
+ private Object toJson(RexFieldCollation collation) {
+ final Map<String, Object> map = jsonBuilder.map();
+ map.put("expr", toJson(collation.left));
+ map.put("direction", collation.getDirection().name());
+ map.put("null-direction", collation.getNullDirection().name());
+ return map;
+ }
+
+ private Object toJson(RexWindowBound windowBound) {
+ final Map<String, Object> map = jsonBuilder.map();
+ if (windowBound.isCurrentRow()) {
+ map.put("type", "CURRENT_ROW");
+ } else if (windowBound.isUnbounded()) {
+ map.put("type", windowBound.isPreceding() ? "UNBOUNDED_PRECEDING" :
"UNBOUNDED_FOLLOWING");
+ } else {
+ map.put("type", windowBound.isPreceding() ? "PRECEDING" : "FOLLOWING");
+ map.put("offset", toJson(windowBound.getOffset()));
+ }
+ return map;
+ }
+
RexNode toRex(RelInput relInput, Object o) {
final RelOptCluster cluster = relInput.getCluster();
final RexBuilder rexBuilder = cluster.getRexBuilder();
@@ -352,16 +438,46 @@ public class RelJson {
final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
if (op != null) {
final List operands = (List) map.get("operands");
- final Object jsonType = map.get("type");
- final SqlOperator operator = toOp(op, map);
final List<RexNode> rexOperands = toRexList(relInput, operands);
- RelDataType type;
- if (jsonType != null) {
- type = toType(typeFactory, jsonType);
+ final Object jsonType = map.get("type");
+ final Map window = (Map) map.get("window");
+ if (window != null) {
+ final SqlAggFunction operator = toAggregation(relInput, op, map);
+ final RelDataType type = toType(typeFactory, jsonType);
+ final List<RexNode> partitionKeys = toRexList(relInput, (List)
window.get("partition"));
+ final List<RexFieldCollation> orderKeys =
+ toRexFieldCollationList(relInput, (List) window.get("order"));
+ final RexWindowBound lowerBound;
+ final RexWindowBound upperBound;
+ final boolean physical;
+ if (window.get("rows-lower") != null) {
+ lowerBound = toRexWindowBound(relInput, (Map)
window.get("rows-lower"));
+ upperBound = toRexWindowBound(relInput, (Map)
window.get("rows-upper"));
+ physical = true;
+ } else if (window.get("range-lower") != null) {
+ lowerBound = toRexWindowBound(relInput, (Map)
window.get("range-lower"));
+ upperBound = toRexWindowBound(relInput, (Map)
window.get("range-upper"));
+ physical = false;
+ } else {
+ // No ROWS or RANGE clause
+ lowerBound = null;
+ upperBound = null;
+ physical = false;
+ }
+ final boolean distinct = (Boolean) map.get("distinct");
+ return rexBuilder.makeOver(type, operator, rexOperands,
partitionKeys,
+ ImmutableList.copyOf(orderKeys), lowerBound, upperBound,
physical, true, false,
+ distinct);
} else {
- type = rexBuilder.deriveReturnType(operator, rexOperands);
+ final SqlOperator operator = toOp(relInput, op, map);
+ final RelDataType type;
+ if (jsonType != null) {
+ type = toType(typeFactory, jsonType);
+ } else {
+ type = rexBuilder.deriveReturnType(operator, rexOperands);
+ }
+ return rexBuilder.makeCall(type, operator, rexOperands);
}
- return rexBuilder.makeCall(type, operator, rexOperands);
}
final Integer input = (Integer) map.get("input");
if (input != null) {
@@ -391,13 +507,17 @@ public class RelJson {
}
if (map.containsKey("literal")) {
final Object literal = map.get("literal");
- final SqlTypeName sqlTypeName =
- Util.enumVal(SqlTypeName.class, (String) map.get("type"));
+ final RelDataType type = toType(typeFactory, map.get("type"));
if (literal == null) {
- return rexBuilder.makeNullLiteral(
- typeFactory.createSqlType(sqlTypeName));
+ return rexBuilder.makeNullLiteral(type);
+ }
+ if (type == null) {
+ // In previous versions, type was not specified for all literals.
+ // To keep backwards compatibility, if type is not specified
+ // we just interpret the literal
+ return toRex(relInput, literal);
}
- return toRex(relInput, literal);
+ return rexBuilder.makeLiteral(literal, type, false);
}
throw new UnsupportedOperationException("cannot convert to rex " + o);
} else if (o instanceof Boolean) {
@@ -418,6 +538,60 @@ public class RelJson {
}
}
+ private List<RexFieldCollation> toRexFieldCollationList(
+ RelInput relInput, List<Map<String, Object>> order) {
+ if (order == null) {
+ return null;
+ }
+
+ List<RexFieldCollation> list = new ArrayList<>();
+ for (Map<String, Object> o : order) {
+ RexNode expr = toRex(relInput, o.get("expr"));
+ Set<SqlKind> directions = new HashSet<>();
+ if (Direction.valueOf((String) o.get("direction")) ==
Direction.DESCENDING) {
+ directions.add(SqlKind.DESCENDING);
+ }
+ if (NullDirection.valueOf((String) o.get("null-direction")) ==
NullDirection.FIRST) {
+ directions.add(SqlKind.NULLS_FIRST);
+ } else {
+ directions.add(SqlKind.NULLS_LAST);
+ }
+ list.add(new RexFieldCollation(expr, directions));
+ }
+ return list;
+ }
+
+ private RexWindowBound toRexWindowBound(RelInput input, Map<String, Object>
map) {
+ if (map == null) {
+ return null;
+ }
+
+ final String type = (String) map.get("type");
+ switch (type) {
+ case "CURRENT_ROW":
+ return RexWindowBound.create(
+ SqlWindow.createCurrentRow(SqlParserPos.ZERO), null);
+ case "UNBOUNDED_PRECEDING":
+ return RexWindowBound.create(
+ SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO), null);
+ case "UNBOUNDED_FOLLOWING":
+ return RexWindowBound.create(
+ SqlWindow.createUnboundedFollowing(SqlParserPos.ZERO), null);
+ case "PRECEDING":
+ RexNode precedingOffset = toRex(input, map.get("offset"));
+ return RexWindowBound.create(null,
+ input.getCluster().getRexBuilder().makeCall(
+ SqlWindow.PRECEDING_OPERATOR, precedingOffset));
+ case "FOLLOWING":
+ RexNode followingOffset = toRex(input, map.get("offset"));
+ return RexWindowBound.create(null,
+ input.getCluster().getRexBuilder().makeCall(
+ SqlWindow.FOLLOWING_OPERATOR, followingOffset));
+ default:
+ throw new UnsupportedOperationException("cannot convert type to rex
window bound " + type);
+ }
+ }
+
private List<RexNode> toRexList(RelInput relInput, List operands) {
final List<RexNode> list = new ArrayList<>();
for (Object operand : operands) {
@@ -426,7 +600,7 @@ public class RelJson {
return list;
}
- private SqlOperator toOp(String op, Map<String, Object> map) {
+ SqlOperator toOp(RelInput relInput, String op, Map<String, Object> map) {
// TODO: build a map, for more efficient lookup
// TODO: look up based on SqlKind
final List<SqlOperator> operatorList =
@@ -443,8 +617,8 @@ public class RelJson {
return null;
}
- SqlAggFunction toAggregation(String agg, Map<String, Object> map) {
- return (SqlAggFunction) toOp(agg, map);
+ SqlAggFunction toAggregation(RelInput relInput, String agg, Map<String,
Object> map) {
+ return (SqlAggFunction) toOp(relInput, agg, map);
}
private String toJson(SqlOperator operator) {
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 29c3f44..4309f96 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
@@ -37,6 +37,7 @@ import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
@@ -76,7 +77,9 @@ public class RelJsonReader {
public RelNode read(String s) throws IOException {
lastRel = null;
final ObjectMapper mapper = new ObjectMapper();
- Map<String, Object> o = mapper.readValue(s, TYPE_REF);
+ Map<String, Object> o = mapper
+ .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
+ .readValue(s, TYPE_REF);
@SuppressWarnings("unchecked")
final List<Map<String, Object>> rels = (List) o.get("rels");
readRels(rels);
@@ -167,7 +170,7 @@ public class RelJsonReader {
final List<Map<String, Object>> jsonAggs = (List) jsonRel.get(tag);
final List<AggregateCall> inputs = new ArrayList<>();
for (Map<String, Object> jsonAggCall : jsonAggs) {
- inputs.add(toAggCall(jsonAggCall));
+ inputs.add(toAggCall(this, jsonAggCall));
}
return inputs;
}
@@ -270,10 +273,10 @@ public class RelJsonReader {
}
}
- private AggregateCall toAggCall(Map<String, Object> jsonAggCall) {
+ private AggregateCall toAggCall(RelInput relInput, Map<String, Object>
jsonAggCall) {
final String aggName = (String) jsonAggCall.get("agg");
final SqlAggFunction aggregation =
- relJson.toAggregation(aggName, jsonAggCall);
+ relJson.toAggregation(relInput, aggName, jsonAggCall);
final Boolean distinct = (Boolean) jsonAggCall.get("distinct");
@SuppressWarnings("unchecked")
final List<Integer> operands = (List<Integer>) jsonAggCall.get("operands");
diff --git
a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
index 2db46a1..fb96778 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
@@ -37,10 +37,10 @@ import java.util.Map;
public class RelJsonWriter implements RelWriter {
//~ Instance fields
----------------------------------------------------------
- private final JsonBuilder jsonBuilder;
- private final RelJson relJson;
+ protected final JsonBuilder jsonBuilder;
+ protected final RelJson relJson;
private final Map<RelNode, String> relIdMap = new IdentityHashMap<>();
- private final List<Object> relList;
+ protected final List<Object> relList;
private final List<Pair<String, Object>> values = new ArrayList<>();
private String previousId;
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWindow.java
b/core/src/main/java/org/apache/calcite/sql/SqlWindow.java
index 4887697..ed3c20d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlWindow.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlWindow.java
@@ -57,14 +57,14 @@ public class SqlWindow extends SqlCall {
/**
* The FOLLOWING operator used exclusively in a window specification.
*/
- static final SqlPostfixOperator FOLLOWING_OPERATOR =
+ public static final SqlPostfixOperator FOLLOWING_OPERATOR =
new SqlPostfixOperator("FOLLOWING", SqlKind.FOLLOWING, 20,
ReturnTypes.ARG0, null,
null);
/**
* The PRECEDING operator used exclusively in a window specification.
*/
- static final SqlPostfixOperator PRECEDING_OPERATOR =
+ public static final SqlPostfixOperator PRECEDING_OPERATOR =
new SqlPostfixOperator("PRECEDING", SqlKind.PRECEDING, 20,
ReturnTypes.ARG0, null,
null);
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 7b63497..a1b3d82 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -24,19 +24,25 @@ import org.apache.calcite.rel.externalize.RelJsonReader;
import org.apache.calcite.rel.externalize.RelJsonWriter;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexFieldCollation;
+import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlExplainFormat;
import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.sql.SqlWindow;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.test.JdbcTest;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.util.ImmutableBitSet;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import org.junit.Test;
@@ -74,7 +80,73 @@ public class RelWriterTest {
+ " \"input\": 1,\n"
+ " \"name\": \"$1\"\n"
+ " },\n"
- + " 10\n"
+ + " {\n"
+ + " \"literal\": 10,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"INTEGER\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"id\": \"2\",\n"
+ + " \"relOp\": \"LogicalAggregate\",\n"
+ + " \"group\": [\n"
+ + " 0\n"
+ + " ],\n"
+ + " \"aggs\": [\n"
+ + " {\n"
+ + " \"agg\": \"COUNT\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"BIGINT\",\n"
+ + " \"nullable\": false\n"
+ + " },\n"
+ + " \"distinct\": true,\n"
+ + " \"operands\": [\n"
+ + " 1\n"
+ + " ]\n"
+ + " },\n"
+ + " {\n"
+ + " \"agg\": \"COUNT\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"BIGINT\",\n"
+ + " \"nullable\": false\n"
+ + " },\n"
+ + " \"distinct\": false,\n"
+ + " \"operands\": []\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+
+ public static final String XXNULL = "{\n"
+ + " \"rels\": [\n"
+ + " {\n"
+ + " \"id\": \"0\",\n"
+ + " \"relOp\": \"LogicalTableScan\",\n"
+ + " \"table\": [\n"
+ + " \"hr\",\n"
+ + " \"emps\"\n"
+ + " ],\n"
+ + " \"inputs\": []\n"
+ + " },\n"
+ + " {\n"
+ + " \"id\": \"1\",\n"
+ + " \"relOp\": \"LogicalFilter\",\n"
+ + " \"condition\": {\n"
+ + " \"op\": \"=\",\n"
+ + " \"operands\": [\n"
+ + " {\n"
+ + " \"input\": 1,\n"
+ + " \"name\": \"$1\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": null,\n"
+ + " \"type\": \"INTEGER\"\n"
+ + " }\n"
+ " ]\n"
+ " }\n"
+ " },\n"
@@ -110,10 +182,122 @@ public class RelWriterTest {
+ " ]\n"
+ "}";
+ public static final String XX2 = "{\n"
+ + " \"rels\": [\n"
+ + " {\n"
+ + " \"id\": \"0\",\n"
+ + " \"relOp\": \"LogicalTableScan\",\n"
+ + " \"table\": [\n"
+ + " \"hr\",\n"
+ + " \"emps\"\n"
+ + " ],\n"
+ + " \"inputs\": []\n"
+ + " },\n"
+ + " {\n"
+ + " \"id\": \"1\",\n"
+ + " \"relOp\": \"LogicalProject\",\n"
+ + " \"fields\": [\n"
+ + " \"field0\",\n"
+ + " \"field1\",\n"
+ + " \"field2\"\n"
+ + " ],\n"
+ + " \"exprs\": [\n"
+ + " {\n"
+ + " \"input\": 0,\n"
+ + " \"name\": \"$0\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"op\": \"COUNT\",\n"
+ + " \"operands\": [\n"
+ + " {\n"
+ + " \"input\": 0,\n"
+ + " \"name\": \"$0\"\n"
+ + " }\n"
+ + " ],\n"
+ + " \"distinct\": false,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"BIGINT\",\n"
+ + " \"nullable\": false\n"
+ + " },\n"
+ + " \"window\": {\n"
+ + " \"partition\": [\n"
+ + " {\n"
+ + " \"input\": 2,\n"
+ + " \"name\": \"$2\"\n"
+ + " }\n"
+ + " ],\n"
+ + " \"order\": [\n"
+ + " {\n"
+ + " \"expr\": {\n"
+ + " \"input\": 1,\n"
+ + " \"name\": \"$1\"\n"
+ + " },\n"
+ + " \"direction\": \"ASCENDING\",\n"
+ + " \"null-direction\": \"LAST\"\n"
+ + " }\n"
+ + " ],\n"
+ + " \"rows-lower\": {\n"
+ + " \"type\": \"UNBOUNDED_PRECEDING\"\n"
+ + " },\n"
+ + " \"rows-upper\": {\n"
+ + " \"type\": \"CURRENT_ROW\"\n"
+ + " }\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"op\": \"SUM\",\n"
+ + " \"operands\": [\n"
+ + " {\n"
+ + " \"input\": 0,\n"
+ + " \"name\": \"$0\"\n"
+ + " }\n"
+ + " ],\n"
+ + " \"distinct\": false,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"BIGINT\",\n"
+ + " \"nullable\": false\n"
+ + " },\n"
+ + " \"window\": {\n"
+ + " \"partition\": [\n"
+ + " {\n"
+ + " \"input\": 2,\n"
+ + " \"name\": \"$2\"\n"
+ + " }\n"
+ + " ],\n"
+ + " \"order\": [\n"
+ + " {\n"
+ + " \"expr\": {\n"
+ + " \"input\": 1,\n"
+ + " \"name\": \"$1\"\n"
+ + " },\n"
+ + " \"direction\": \"ASCENDING\",\n"
+ + " \"null-direction\": \"LAST\"\n"
+ + " }\n"
+ + " ],\n"
+ + " \"range-lower\": {\n"
+ + " \"type\": \"CURRENT_ROW\"\n"
+ + " },\n"
+ + " \"range-upper\": {\n"
+ + " \"type\": \"FOLLOWING\",\n"
+ + " \"offset\": {\n"
+ + " \"literal\": 1,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"INTEGER\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonWriter} on
- * a simple tree of relational expressions, consisting of a table, a filter
- * and an aggregate node.
+ * a simple tree of relational expressions, consisting of a table and a
+ * project including window expressions.
*/
@Test public void testWriter() {
String s =
@@ -152,6 +336,63 @@ public class RelWriterTest {
}
/**
+ * Unit test for {@link org.apache.calcite.rel.externalize.RelJsonWriter} on
+ * a simple tree of relational expressions, consisting of a table, a filter
+ * and an aggregate node.
+ */
+ @Test public void testWriter2() {
+ String s =
+ Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
+ rootSchema.add("hr",
+ new ReflectiveSchema(new JdbcTest.HrSchema()));
+ LogicalTableScan scan =
+ LogicalTableScan.create(cluster,
+ relOptSchema.getTableForMember(
+ Arrays.asList("hr", "emps")));
+ final RexBuilder rexBuilder = cluster.getRexBuilder();
+ final RelDataType bigIntType =
+ cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
+ LogicalProject project =
+ LogicalProject.create(scan,
+ ImmutableList.of(
+ rexBuilder.makeInputRef(scan, 0),
+ rexBuilder.makeOver(bigIntType,
+ SqlStdOperatorTable.COUNT,
+ ImmutableList.of(rexBuilder.makeInputRef(scan, 0)),
+ ImmutableList.of(rexBuilder.makeInputRef(scan, 2)),
+ ImmutableList.of(
+ new RexFieldCollation(
+ rexBuilder.makeInputRef(scan, 1),
ImmutableSet.of())),
+ RexWindowBound.create(
+
SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO), null),
+ RexWindowBound.create(
+ SqlWindow.createCurrentRow(SqlParserPos.ZERO),
null),
+ true, true, false, false),
+ rexBuilder.makeOver(bigIntType,
+ SqlStdOperatorTable.SUM,
+ ImmutableList.of(rexBuilder.makeInputRef(scan, 0)),
+ ImmutableList.of(rexBuilder.makeInputRef(scan, 2)),
+ ImmutableList.of(
+ new RexFieldCollation(
+ rexBuilder.makeInputRef(scan, 1),
ImmutableSet.of())),
+ RexWindowBound.create(
+ SqlWindow.createCurrentRow(SqlParserPos.ZERO),
null),
+ RexWindowBound.create(null,
+ rexBuilder.makeCall(
+ SqlWindow.FOLLOWING_OPERATOR,
+
rexBuilder.makeExactLiteral(BigDecimal.ONE))),
+ false, true, false, false)
+ ),
+ ImmutableList.of("field0", "field1", "field2"));
+ final RelJsonWriter writer = new RelJsonWriter();
+ project.explain(writer);
+ return writer.asString();
+ });
+ assertThat(s, is(XX2));
+ }
+
+
+ /**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonReader}.
*/
@Test public void testReader() {
@@ -177,6 +418,61 @@ public class RelWriterTest {
+ " LogicalFilter(condition=[=($1, 10)])\n"
+ " LogicalTableScan(table=[[hr, emps]])\n"));
}
+
+ /**
+ * Unit test for {@link org.apache.calcite.rel.externalize.RelJsonReader}.
+ */
+ @Test public void testReader2() {
+ String s =
+ Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
+ SchemaPlus schema =
+ rootSchema.add("hr",
+ new ReflectiveSchema(new JdbcTest.HrSchema()));
+ final RelJsonReader reader =
+ new RelJsonReader(cluster, relOptSchema, schema);
+ RelNode node;
+ try {
+ node = reader.read(XX2);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
+ SqlExplainLevel.EXPPLAN_ATTRIBUTES);
+ });
+
+ assertThat(s,
+ isLinux("LogicalProject(field0=[$0],"
+ + " field1=[COUNT($0) OVER (PARTITION BY $2 ORDER BY $1 NULLS LAST
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)],"
+ + " field2=[SUM($0) OVER (PARTITION BY $2 ORDER BY $1 NULLS LAST
RANGE BETWEEN CURRENT ROW AND 1 FOLLOWING)])\n"
+ + " LogicalTableScan(table=[[hr, emps]])\n"));
+ }
+
+ /**
+ * Unit test for {@link org.apache.calcite.rel.externalize.RelJsonReader}.
+ */
+ @Test public void testReaderNull() {
+ String s =
+ Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
+ SchemaPlus schema =
+ rootSchema.add("hr",
+ new ReflectiveSchema(new JdbcTest.HrSchema()));
+ final RelJsonReader reader =
+ new RelJsonReader(cluster, relOptSchema, schema);
+ RelNode node;
+ try {
+ node = reader.read(XXNULL);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
+ SqlExplainLevel.EXPPLAN_ATTRIBUTES);
+ });
+
+ assertThat(s,
+ isLinux("LogicalAggregate(group=[{0}], agg#0=[COUNT(DISTINCT $1)],
agg#1=[COUNT()])\n"
+ + " LogicalFilter(condition=[=($1, null:INTEGER)])\n"
+ + " LogicalTableScan(table=[[hr, emps]])\n"));
+ }
}
// End RelWriterTest.java
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 3674078..a4aa8df 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -5542,19 +5542,62 @@ public class JdbcTest {
+ " \"nullable\": false,\n"
+ " \"precision\": 2,\n"
+ " \"name\": \"EXPR$1\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"type\": \"TIMESTAMP\",\n"
+ + " \"nullable\": false,\n"
+ + " \"precision\": 0,\n"
+ + " \"name\": \"EXPR$2\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"type\": \"DECIMAL\",\n"
+ + " \"nullable\": false,\n"
+ + " \"precision\": 3,\n"
+ + " \"scale\": 2,\n"
+ + " \"name\": \"EXPR$3\"\n"
+ " }\n"
+ " ],\n"
+ " \"tuples\": [\n"
+ " [\n"
- + " 1,\n"
- + " \"ab\"\n"
+ + " {\n"
+ + " \"literal\": 1,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"INTEGER\",\n"
+ + " \"nullable\": false\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": \"ab\",\n"
+ + " \"type\": {\n"
+ + " \"type\": \"CHAR\",\n"
+ + " \"nullable\": false,\n"
+ + " \"precision\": 2\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": 1364860800000,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"TIMESTAMP\",\n"
+ + " \"nullable\": false,\n"
+ + " \"precision\": 0\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"literal\": 0.01,\n"
+ + " \"type\": {\n"
+ + " \"type\": \"DECIMAL\",\n"
+ + " \"nullable\": false,\n"
+ + " \"precision\": 3,\n"
+ + " \"scale\": 2\n"
+ + " }\n"
+ + " }\n"
+ " ]\n"
+ " ],\n"
+ " \"inputs\": []\n"
+ " }\n"
+ " ]\n"
+ "}\n";
- with.query("explain plan as json for values (1, 'ab')")
+ with.query("explain plan as json for values (1, 'ab', TIMESTAMP
'2013-04-02 00:00:00', 0.01)")
.returns(expectedJson);
with.query("explain plan with implementation for values (1, 'ab')")
.returns("PLAN=EnumerableValues(tuples=[[{ 1, 'ab' }]])\n\n");