All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.rel.externalize.RelJson Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hazelcast.org.apache.calcite.rel.externalize;

import com.hazelcast.org.apache.calcite.avatica.AvaticaUtils;
import com.hazelcast.org.apache.calcite.avatica.util.TimeUnit;
import com.hazelcast.org.apache.calcite.plan.RelOptCluster;
import com.hazelcast.org.apache.calcite.plan.RelOptTable;
import com.hazelcast.org.apache.calcite.plan.RelTraitSet;
import com.hazelcast.org.apache.calcite.rel.RelCollation;
import com.hazelcast.org.apache.calcite.rel.RelCollationImpl;
import com.hazelcast.org.apache.calcite.rel.RelCollations;
import com.hazelcast.org.apache.calcite.rel.RelDistribution;
import com.hazelcast.org.apache.calcite.rel.RelDistributions;
import com.hazelcast.org.apache.calcite.rel.RelFieldCollation;
import com.hazelcast.org.apache.calcite.rel.RelFieldCollation.Direction;
import com.hazelcast.org.apache.calcite.rel.RelFieldCollation.NullDirection;
import com.hazelcast.org.apache.calcite.rel.RelInput;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.core.AggregateCall;
import com.hazelcast.org.apache.calcite.rel.core.CorrelationId;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.rex.RexBuilder;
import com.hazelcast.org.apache.calcite.rex.RexCall;
import com.hazelcast.org.apache.calcite.rex.RexCorrelVariable;
import com.hazelcast.org.apache.calcite.rex.RexFieldAccess;
import com.hazelcast.org.apache.calcite.rex.RexFieldCollation;
import com.hazelcast.org.apache.calcite.rex.RexLiteral;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexOver;
import com.hazelcast.org.apache.calcite.rex.RexSlot;
import com.hazelcast.org.apache.calcite.rex.RexWindow;
import com.hazelcast.org.apache.calcite.rex.RexWindowBound;
import com.hazelcast.org.apache.calcite.rex.RexWindowBounds;
import com.hazelcast.org.apache.calcite.sql.SqlAggFunction;
import com.hazelcast.org.apache.calcite.sql.SqlFunction;
import com.hazelcast.org.apache.calcite.sql.SqlIdentifier;
import com.hazelcast.org.apache.calcite.sql.SqlIntervalQualifier;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.SqlSyntax;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;
import com.hazelcast.org.apache.calcite.sql.validate.SqlNameMatchers;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import com.hazelcast.org.apache.calcite.util.ImmutableIntList;
import com.hazelcast.org.apache.calcite.util.JsonBuilder;
import com.hazelcast.org.apache.calcite.util.Util;

import com.hazelcast.com.google.common.collect.ImmutableList;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import com.hazelcast.org.checkerframework.checker.nullness.qual.PolyNull;

import java.lang.reflect.Constructor;
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;

import static com.hazelcast.org.apache.calcite.rel.RelDistributions.EMPTY;
import static com.hazelcast.org.apache.calcite.util.Static.RESOURCE;

import static java.util.Objects.requireNonNull;

/**
 * Utilities for converting {@link com.hazelcast.org.apache.calcite.rel.RelNode}
 * into JSON format.
 */
public class RelJson {
  private final Map constructorMap = new HashMap<>();
  private final @Nullable JsonBuilder jsonBuilder;
  private final InputTranslator inputTranslator;

  public static final List PACKAGES =
      ImmutableList.of(
          "com.hazelcast.org.apache.calcite.rel.",
          "com.hazelcast.org.apache.calcite.rel.core.",
          "com.hazelcast.org.apache.calcite.rel.logical.",
          "com.hazelcast.org.apache.calcite.adapter.jdbc.",
          "com.hazelcast.org.apache.calcite.adapter.jdbc.JdbcRules$");

  /** Private constructor. */
  private RelJson(@Nullable JsonBuilder jsonBuilder,
      InputTranslator inputTranslator) {
    this.jsonBuilder = jsonBuilder;
    this.inputTranslator = requireNonNull(inputTranslator, "inputTranslator");
  }

  /** Creates a RelJson. */
  public RelJson(@Nullable JsonBuilder jsonBuilder) {
    this(jsonBuilder, RelJson::translateInput);
  }

  /** Returns a RelJson with a given InputTranslator. */
  public RelJson withInputTranslator(InputTranslator inputTranslator) {
    if (inputTranslator == this.inputTranslator) {
      return this;
    }
    return new RelJson(jsonBuilder, inputTranslator);
  }

  private JsonBuilder jsonBuilder() {
    return requireNonNull(jsonBuilder, "jsonBuilder");
  }

  @SuppressWarnings("unchecked")
  private static  T get(Map map,
      String key) {
    return (T) requireNonNull(map.get(key), () -> "entry for key " + key);
  }

  private static > T enumVal(Class clazz, Map map,
      String key) {
    String textValue = get(map, key);
    return requireNonNull(
        Util.enumVal(clazz, textValue),
        () -> "unable to find enum value " + textValue + " in class " + clazz);
  }

  public RelNode create(Map map) {
    String type = get(map, "type");
    Constructor constructor = getConstructor(type);
    try {
      return (RelNode) constructor.newInstance(map);
    } catch (InstantiationException | ClassCastException | InvocationTargetException
        | IllegalAccessException e) {
      throw new RuntimeException(
          "while invoking constructor for type '" + type + "'", e);
    }
  }

  public Constructor getConstructor(String type) {
    Constructor constructor = constructorMap.get(type);
    if (constructor == null) {
      Class clazz = typeNameToClass(type);
      try {
        //noinspection unchecked
        constructor = clazz.getConstructor(RelInput.class);
      } catch (NoSuchMethodException e) {
        throw new RuntimeException("class does not have required constructor, "
            + clazz + "(RelInput)");
      }
      constructorMap.put(type, constructor);
    }
    return constructor;
  }

  /**
   * Converts a type name to a class. E.g. {@code getClass("LogicalProject")}
   * returns {@link com.hazelcast.org.apache.calcite.rel.logical.LogicalProject}.class.
   */
  public Class typeNameToClass(String type) {
    if (!type.contains(".")) {
      for (String package_ : PACKAGES) {
        try {
          return Class.forName(package_ + type);
        } catch (ClassNotFoundException e) {
          // ignore
        }
      }
    }
    try {
      return Class.forName(type);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException("unknown type " + type);
    }
  }

  /**
   * Inverse of {@link #typeNameToClass}.
   */
  public String classToTypeName(Class class_) {
    final String canonicalName = class_.getName();
    for (String package_ : PACKAGES) {
      if (canonicalName.startsWith(package_)) {
        String remaining = canonicalName.substring(package_.length());
        if (remaining.indexOf('.') < 0 && remaining.indexOf('$') < 0) {
          return remaining;
        }
      }
    }
    return canonicalName;
  }

  /** Default implementation of
   * {@link InputTranslator#translateInput(RelJson, int, Map, RelInput)}. */
  private static RexNode translateInput(RelJson relJson, int input,
      Map map, RelInput relInput) {
    final RelOptCluster cluster = relInput.getCluster();
    final RexBuilder rexBuilder = cluster.getRexBuilder();

    // Check if it is a local ref.
    if (map.containsKey("type")) {
      final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
      final RelDataType type = relJson.toType(typeFactory, get(map, "type"));
      return rexBuilder.makeLocalRef(type, input);
    }
    int i = input;
    final List relNodes = relInput.getInputs();
    for (RelNode inputNode : relNodes) {
      final RelDataType rowType = inputNode.getRowType();
      if (i < rowType.getFieldCount()) {
        final RelDataTypeField field = rowType.getFieldList().get(i);
        return rexBuilder.makeInputRef(field.getType(), input);
      }
      i -= rowType.getFieldCount();
    }
    throw new RuntimeException("input field " + input + " is out of range");
  }

  public Object toJson(RelCollationImpl node) {
    final List list = new ArrayList<>();
    for (RelFieldCollation fieldCollation : node.getFieldCollations()) {
      final Map map = jsonBuilder().map();
      map.put("field", fieldCollation.getFieldIndex());
      map.put("direction", fieldCollation.getDirection().name());
      map.put("nulls", fieldCollation.nullDirection.name());
      list.add(map);
    }
    return list;
  }

  public RelCollation toCollation(
      List> jsonFieldCollations) {
    final List fieldCollations = new ArrayList<>();
    for (Map map : jsonFieldCollations) {
      fieldCollations.add(toFieldCollation(map));
    }
    return RelCollations.of(fieldCollations);
  }

  public RelFieldCollation toFieldCollation(Map map) {
    final Integer field = get(map, "field");
    final RelFieldCollation.Direction direction =
        enumVal(RelFieldCollation.Direction.class,
            map, "direction");
    final RelFieldCollation.NullDirection nullDirection =
        enumVal(RelFieldCollation.NullDirection.class,
            map, "nulls");
    return new RelFieldCollation(field, direction, nullDirection);
  }

  public RelDistribution toDistribution(Map map) {
    final RelDistribution.Type type =
        enumVal(RelDistribution.Type.class,
            map, "type");

    ImmutableIntList list = EMPTY;
    List keys = (List) map.get("keys");
    if (keys != null) {
      list = ImmutableIntList.copyOf(keys);
    }
    return RelDistributions.of(type, list);
  }

  private Object toJson(RelDistribution relDistribution) {
    final Map map = jsonBuilder().map();
    map.put("type", relDistribution.getType().name());
    if (!relDistribution.getKeys().isEmpty()) {
      map.put("keys", relDistribution.getKeys());
    }
    return map;
  }

  public RelDataType toType(RelDataTypeFactory typeFactory, Object o) {
    if (o instanceof List) {
      @SuppressWarnings("unchecked")
      final List> jsonList = (List>) o;
      final RelDataTypeFactory.Builder builder = typeFactory.builder();
      for (Map jsonMap : jsonList) {
        builder.add(get(jsonMap, "name"), toType(typeFactory, jsonMap));
      }
      return builder.build();
    } else if (o instanceof Map) {
      @SuppressWarnings("unchecked")
      final Map map = (Map) o;
      final RelDataType type = getRelDataType(typeFactory, map);
      final boolean nullable = get(map, "nullable");
      return typeFactory.createTypeWithNullability(type, nullable);
    } else {
      final SqlTypeName sqlTypeName = requireNonNull(
          Util.enumVal(SqlTypeName.class, (String) o),
          () -> "unable to find enum value " + o + " in class " + SqlTypeName.class);
      return typeFactory.createSqlType(sqlTypeName);
    }
  }

  private RelDataType getRelDataType(RelDataTypeFactory typeFactory, Map map) {
    final Object fields = map.get("fields");
    if (fields != null) {
      // Nested struct
      return toType(typeFactory, fields);
    }
    final SqlTypeName sqlTypeName =
        enumVal(SqlTypeName.class, map, "type");
    final Object component;
    final RelDataType componentType;
    switch (sqlTypeName) {
    case INTERVAL_YEAR:
    case INTERVAL_YEAR_MONTH:
    case INTERVAL_MONTH:
    case INTERVAL_DAY:
    case INTERVAL_DAY_HOUR:
    case INTERVAL_DAY_MINUTE:
    case INTERVAL_DAY_SECOND:
    case INTERVAL_HOUR:
    case INTERVAL_HOUR_MINUTE:
    case INTERVAL_HOUR_SECOND:
    case INTERVAL_MINUTE:
    case INTERVAL_MINUTE_SECOND:
    case INTERVAL_SECOND:
      TimeUnit startUnit = sqlTypeName.getStartUnit();
      TimeUnit endUnit = sqlTypeName.getEndUnit();
      return typeFactory.createSqlIntervalType(
          new SqlIntervalQualifier(startUnit, endUnit, SqlParserPos.ZERO));

    case ARRAY:
      component = requireNonNull(map.get("component"), "component");
      componentType = toType(typeFactory, component);
      return typeFactory.createArrayType(componentType, -1);

    case MAP:
      Object key = get(map, "key");
      Object value = get(map, "value");
      RelDataType keyType = toType(typeFactory, key);
      RelDataType valueType = toType(typeFactory, value);
      return typeFactory.createMapType(keyType, valueType);

    case MULTISET:
      component = requireNonNull(map.get("component"), "component");
      componentType = toType(typeFactory, component);
      return typeFactory.createMultisetType(componentType, -1);

    default:
      final Integer precision = (Integer) map.get("precision");
      final Integer scale = (Integer) map.get("scale");
      if (precision == null) {
        return typeFactory.createSqlType(sqlTypeName);
      } else if (scale == null) {
        return typeFactory.createSqlType(sqlTypeName, precision);
      } else {
        return typeFactory.createSqlType(sqlTypeName, precision, scale);
      }
    }
  }

  public Object toJson(AggregateCall node) {
    final Map map = jsonBuilder().map();
    final Map aggMap = toJson(node.getAggregation());
    if (node.getAggregation().getFunctionType().isUserDefined()) {
      aggMap.put("class", node.getAggregation().getClass().getName());
    }
    map.put("agg", aggMap);
    map.put("type", toJson(node.getType()));
    map.put("distinct", node.isDistinct());
    map.put("operands", node.getArgList());
    map.put("name", node.getName());
    return map;
  }

  public @Nullable Object toJson(@Nullable Object value) {
    if (value == null
        || value instanceof Number
        || value instanceof String
        || value instanceof Boolean) {
      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) {
      final List<@Nullable Object> list = jsonBuilder().list();
      for (Object o : (List) value) {
        list.add(toJson(o));
      }
      return list;
    } else if (value instanceof ImmutableBitSet) {
      final List<@Nullable Object> list = jsonBuilder().list();
      for (Integer integer : (ImmutableBitSet) value) {
        list.add(toJson(integer));
      }
      return list;
    } else if (value instanceof AggregateCall) {
      return toJson((AggregateCall) value);
    } else if (value instanceof RelCollationImpl) {
      return toJson((RelCollationImpl) value);
    } else if (value instanceof RelDataType) {
      return toJson((RelDataType) value);
    } else if (value instanceof RelDataTypeField) {
      return toJson((RelDataTypeField) value);
    } else if (value instanceof RelDistribution) {
      return toJson((RelDistribution) value);
    } else {
      throw new UnsupportedOperationException("type not serializable: "
          + value + " (type " + value.getClass().getCanonicalName() + ")");
    }
  }

  private Object toJson(RelDataType node) {
    final Map map = jsonBuilder().map();
    if (node.isStruct()) {
      final List<@Nullable Object> list = jsonBuilder().list();
      for (RelDataTypeField field : node.getFieldList()) {
        list.add(toJson(field));
      }
      map.put("fields", list);
      map.put("nullable", node.isNullable());
    } else {
      map.put("type", node.getSqlTypeName().name());
      map.put("nullable", node.isNullable());
      if (node.getComponentType() != null) {
        map.put("component", toJson(node.getComponentType()));
      }
      RelDataType keyType = node.getKeyType();
      if (keyType != null) {
        map.put("key", toJson(keyType));
      }
      RelDataType valueType = node.getValueType();
      if (valueType != null) {
        map.put("value", toJson(valueType));
      }
      if (node.getSqlTypeName().allowsPrec()) {
        map.put("precision", node.getPrecision());
      }
      if (node.getSqlTypeName().allowsScale()) {
        map.put("scale", node.getScale());
      }
    }
    return map;
  }

  private Object toJson(RelDataTypeField node) {
    final Map map;
    if (node.getType().isStruct()) {
      map = jsonBuilder().map();
      map.put("fields", toJson(node.getType()));
      map.put("nullable", node.getType().isNullable());
    } else {
      //noinspection unchecked
      map = (Map) toJson(node.getType());
    }
    map.put("name", node.getName());
    return map;
  }

  private static Object toJson(CorrelationId node) {
    return node.getId();
  }

  private Object toJson(RexNode node) {
    final Map map;
    switch (node.getKind()) {
    case FIELD_ACCESS:
      map = jsonBuilder().map();
      final RexFieldAccess fieldAccess = (RexFieldAccess) node;
      map.put("field", fieldAccess.getField().getName());
      map.put("expr", toJson(fieldAccess.getReferenceExpr()));
      return map;
    case LITERAL:
      final RexLiteral literal = (RexLiteral) node;
      final Object value = literal.getValue3();
      map = jsonBuilder().map();
      map.put("literal", RelEnumTypes.fromEnum(value));
      map.put("type", toJson(node.getType()));
      return map;
    case INPUT_REF:
      map = jsonBuilder().map();
      map.put("input", ((RexSlot) node).getIndex());
      map.put("name", ((RexSlot) node).getName());
      return map;
    case LOCAL_REF:
      map = jsonBuilder().map();
      map.put("input", ((RexSlot) node).getIndex());
      map.put("name", ((RexSlot) node).getName());
      map.put("type", toJson(node.getType()));
      return map;
    case CORREL_VARIABLE:
      map = jsonBuilder().map();
      map.put("correl", ((RexCorrelVariable) node).getName());
      map.put("type", toJson(node.getType()));
      return map;
    default:
      if (node instanceof RexCall) {
        final RexCall call = (RexCall) node;
        map = jsonBuilder().map();
        map.put("op", toJson(call.getOperator()));
        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());
          }
        }
        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 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 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 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");
      RexNode offset = requireNonNull(windowBound.getOffset(),
          () -> "getOffset for window bound " + windowBound);
      map.put("offset", toJson(offset));
    }
    return map;
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  @PolyNull RexNode toRex(RelInput relInput, @PolyNull Object o) {
    final RelOptCluster cluster = relInput.getCluster();
    final RexBuilder rexBuilder = cluster.getRexBuilder();
    if (o == null) {
      return null;
    } else if (o instanceof Map) {
      final Map map = (Map) o;
      final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
      if (map.containsKey("op")) {
        final Map opMap = get(map, "op");
        if (map.containsKey("class")) {
          opMap.put("class", get(map, "class"));
        }
        final List operands = get(map, "operands");
        final List rexOperands = toRexList(relInput, operands);
        final Object jsonType = map.get("type");
        final Map window = (Map) map.get("window");
        if (window != null) {
          final SqlAggFunction operator = requireNonNull(toAggregation(opMap), "operator");
          final RelDataType type = toType(typeFactory, requireNonNull(jsonType, "jsonType"));
          List partitionKeys = new ArrayList<>();
          Object partition = window.get("partition");
          if (partition != null) {
            partitionKeys = toRexList(relInput, (List) partition);
          }
          List orderKeys = new ArrayList<>();
          if (window.containsKey("order")) {
            addRexFieldCollationList(orderKeys, 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
            // Note: lower and upper bounds are non-nullable, so this branch is not reachable
            lowerBound = null;
            upperBound = null;
            physical = false;
          }
          final boolean distinct = get((Map) map, "distinct");
          return rexBuilder.makeOver(type, operator, rexOperands, partitionKeys,
              ImmutableList.copyOf(orderKeys),
              requireNonNull(lowerBound, "lowerBound"),
              requireNonNull(upperBound, "upperBound"),
              physical,
              true, false, distinct, false);
        } else {
          final SqlOperator operator = requireNonNull(toOp(opMap), "operator");
          final RelDataType type;
          if (jsonType != null) {
            type = toType(typeFactory, jsonType);
          } else {
            type = rexBuilder.deriveReturnType(operator, rexOperands);
          }
          return rexBuilder.makeCall(type, operator, rexOperands);
        }
      }
      final Integer input = (Integer) map.get("input");
      if (input != null) {
        return inputTranslator.translateInput(this, input, map, relInput);
      }
      final String field = (String) map.get("field");
      if (field != null) {
        final Object jsonExpr = get(map, "expr");
        final RexNode expr = toRex(relInput, jsonExpr);
        return rexBuilder.makeFieldAccess(expr, field, true);
      }
      final String correl = (String) map.get("correl");
      if (correl != null) {
        final Object jsonType = get(map, "type");
        RelDataType type = toType(typeFactory, jsonType);
        return rexBuilder.makeCorrel(type, new CorrelationId(correl));
      }
      if (map.containsKey("literal")) {
        Object literal = map.get("literal");
        if (literal == null) {
          final RelDataType type = toType(typeFactory, get(map, "type"));
          return rexBuilder.makeNullLiteral(type);
        }
        if (!map.containsKey("type")) {
          // 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);
        }
        final RelDataType type = toType(typeFactory, get(map, "type"));
        if (type.getSqlTypeName() == SqlTypeName.SYMBOL) {
          literal = RelEnumTypes.toEnum((String) literal);
        }
        return rexBuilder.makeLiteral(literal, type);
      }
      throw new UnsupportedOperationException("cannot convert to rex " + o);
    } else if (o instanceof Boolean) {
      return rexBuilder.makeLiteral((Boolean) o);
    } else if (o instanceof String) {
      return rexBuilder.makeLiteral((String) o);
    } else if (o instanceof Number) {
      final Number number = (Number) o;
      if (number instanceof Double || number instanceof Float) {
        return rexBuilder.makeApproxLiteral(
            BigDecimal.valueOf(number.doubleValue()));
      } else {
        return rexBuilder.makeExactLiteral(
            BigDecimal.valueOf(number.longValue()));
      }
    } else {
      throw new UnsupportedOperationException("cannot convert to rex " + o);
    }
  }

  private void addRexFieldCollationList(
      List list,
      RelInput relInput, @Nullable List> order) {
    if (order == null) {
      return;
    }

    for (Map o : order) {
      RexNode expr = requireNonNull(toRex(relInput, o.get("expr")), "expr");
      Set directions = new HashSet<>();
      if (Direction.valueOf(get(o, "direction")) == Direction.DESCENDING) {
        directions.add(SqlKind.DESCENDING);
      }
      if (NullDirection.valueOf(get(o, "null-direction")) == NullDirection.FIRST) {
        directions.add(SqlKind.NULLS_FIRST);
      } else {
        directions.add(SqlKind.NULLS_LAST);
      }
      list.add(new RexFieldCollation(expr, directions));
    }
  }

  private @Nullable RexWindowBound toRexWindowBound(RelInput input,
      @Nullable Map map) {
    if (map == null) {
      return null;
    }

    final String type = get(map, "type");
    switch (type) {
    case "CURRENT_ROW":
      return RexWindowBounds.CURRENT_ROW;
    case "UNBOUNDED_PRECEDING":
      return RexWindowBounds.UNBOUNDED_PRECEDING;
    case "UNBOUNDED_FOLLOWING":
      return RexWindowBounds.UNBOUNDED_FOLLOWING;
    case "PRECEDING":
      return RexWindowBounds.preceding(toRex(input, get(map, "offset")));
    case "FOLLOWING":
      return RexWindowBounds.following(toRex(input, get(map, "offset")));
    default:
      throw new UnsupportedOperationException("cannot convert type to rex window bound " + type);
    }
  }

  private List toRexList(RelInput relInput, List operands) {
    final List list = new ArrayList<>();
    for (Object operand : operands) {
      list.add(toRex(relInput, operand));
    }
    return list;
  }

  @Nullable SqlOperator toOp(Map map) {
    // in case different operator has the same kind, check with both name and kind.
    String name = get(map, "name");
    String kind = get(map, "kind");
    String syntax = get(map, "syntax");
    SqlKind sqlKind = SqlKind.valueOf(kind);
    SqlSyntax  sqlSyntax = SqlSyntax.valueOf(syntax);
    List operators = new ArrayList<>();
    SqlStdOperatorTable.instance().lookupOperatorOverloads(
        new SqlIdentifier(name, new SqlParserPos(0, 0)),
        null,
        sqlSyntax,
        operators,
        SqlNameMatchers.liberal());
    for (SqlOperator operator: operators) {
      if (operator.kind == sqlKind) {
        return operator;
      }
    }
    String class_ = (String) map.get("class");
    if (class_ != null) {
      return AvaticaUtils.instantiatePlugin(SqlOperator.class, class_);
    }
    throw RESOURCE.noOperator(name, kind, syntax).ex();
  }

  @Nullable SqlAggFunction toAggregation(Map map) {
    return (SqlAggFunction) toOp(map);
  }

  private Map toJson(SqlOperator operator) {
    // User-defined operators are not yet handled.
    Map map = jsonBuilder().map();
    map.put("name", operator.getName());
    map.put("kind", operator.kind.toString());
    map.put("syntax", operator.getSyntax().toString());
    return map;
  }

  /**
   * Translates a JSON expression into a RexNode,
   * using a given {@link InputTranslator} to transform JSON objects that
   * represent input references into RexNodes.
   *
   * @param cluster The optimization environment
   * @param translator Input translator
   * @param o JSON object
   * @return the transformed RexNode
   */
  public static RexNode readExpression(RelOptCluster cluster,
      InputTranslator translator, Map o) {
    RelInput relInput = new RelInputForCluster(cluster);
    return new RelJson(null, translator).toRex(relInput, o);
  }

  /**
   * Special context from which a relational expression can be initialized,
   * reading from a serialized form of the relational expression.
   *
   * 

Contains only a cluster and an empty list of inputs; * most methods throw {@link UnsupportedOperationException}. */ private static class RelInputForCluster implements RelInput { private final RelOptCluster cluster; RelInputForCluster(RelOptCluster cluster) { this.cluster = cluster; } @Override public RelOptCluster getCluster() { return cluster; } @Override public RelTraitSet getTraitSet() { throw new UnsupportedOperationException(); } @Override public RelOptTable getTable(String table) { throw new UnsupportedOperationException(); } @Override public RelNode getInput() { throw new UnsupportedOperationException(); } @Override public List getInputs() { return ImmutableList.of(); } @Override public @Nullable RexNode getExpression(String tag) { throw new UnsupportedOperationException(); } @Override public ImmutableBitSet getBitSet(String tag) { throw new UnsupportedOperationException(); } @Override public @Nullable List getBitSetList(String tag) { throw new UnsupportedOperationException(); } @Override public List getAggregateCalls(String tag) { throw new UnsupportedOperationException(); } @Override public @Nullable Object get(String tag) { throw new UnsupportedOperationException(); } @Override public @Nullable String getString(String tag) { throw new UnsupportedOperationException(); } @Override public float getFloat(String tag) { throw new UnsupportedOperationException(); } @Override public > @Nullable E getEnum( String tag, Class enumClass) { throw new UnsupportedOperationException(); } @Override public @Nullable List getExpressionList(String tag) { throw new UnsupportedOperationException(); } @Override public @Nullable List getStringList(String tag) { throw new UnsupportedOperationException(); } @Override public @Nullable List getIntegerList(String tag) { throw new UnsupportedOperationException(); } @Override public @Nullable List> getIntegerListList(String tag) { throw new UnsupportedOperationException(); } @Override public RelDataType getRowType(String tag) { throw new UnsupportedOperationException(); } @Override public RelDataType getRowType(String expressionsTag, String fieldsTag) { throw new UnsupportedOperationException(); } @Override public RelCollation getCollation() { throw new UnsupportedOperationException(); } @Override public RelDistribution getDistribution() { throw new UnsupportedOperationException(); } @Override public ImmutableList> getTuples(String tag) { throw new UnsupportedOperationException(); } @Override public boolean getBoolean(String tag, boolean default_) { throw new UnsupportedOperationException(); } } /** * Translates a JSON object that represents an input reference into a RexNode. */ @FunctionalInterface public interface InputTranslator { /** * Transforms an input reference into a RexNode. * * @param relJson RelJson * @param input Ordinal of input field * @param map JSON object representing an input reference * @param relInput Description of input(s) * @return RexNode representing an input reference */ RexNode translateInput(RelJson relJson, int input, Map map, RelInput relInput); } }