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

Reply via email to