/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde 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 org.eigenbase.rel;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.*;
import org.eigenbase.relopt.RelOptCluster;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.*;
import org.eigenbase.sql.SqlFunction;
import org.eigenbase.sql.SqlOperator;
import org.eigenbase.sql.fun.SqlStdOperatorTable;
import org.eigenbase.sql.type.SqlTypeName;
import org.eigenbase.util.JsonBuilder;
import org.eigenbase.util.Util;
import net.hydromatic.optiq.util.BitSets;
import com.google.common.collect.ImmutableList;
/**
* Utilities for converting {@link RelNode} into JSON format.
*/
public class RelJson {
private final Map<String, Constructor> constructorMap =
new HashMap<String, Constructor>();
private final JsonBuilder jsonBuilder;
public static final List<String> PACKAGES =
ImmutableList.of(
"org.eigenbase.rel.",
"net.hydromatic.optiq.impl.jdbc.",
"net.hydromatic.optiq.impl.jdbc.JdbcRules$");
public RelJson(JsonBuilder jsonBuilder) {
this.jsonBuilder = jsonBuilder;
}
public RelNode create(Map<String, Object> map) {
String type = (String) map.get("type");
Constructor constructor = getConstructor(type);
try {
return (RelNode) constructor.newInstance(map);
} catch (InstantiationException e) {
throw new RuntimeException(
"while invoking constructor for type '" + type + "'", e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"while invoking constructor for type '" + type + "'", e);
} catch (InvocationTargetException e) {
throw new RuntimeException(
"while invoking constructor for type '" + type + "'", e);
} catch (ClassCastException 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("ProjectRel")}
* returns {@link org.eigenbase.rel.ProjectRel}.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<? extends RelNode> 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;
}
public Object toJson(RelCollationImpl node) {
final List<Object> list = new ArrayList<Object>();
for (RelFieldCollation fieldCollation : node.getFieldCollations()) {
final Map<String, Object> 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<Map<String, Object>> jsonFieldCollations) {
final List<RelFieldCollation> fieldCollations =
new ArrayList<RelFieldCollation>();
for (Map<String, Object> map : jsonFieldCollations) {
fieldCollations.add(toFieldCollation(map));
}
return RelCollationImpl.of(fieldCollations);
}
public RelFieldCollation toFieldCollation(Map<String, Object> map) {
final Integer field = (Integer) map.get("field");
final RelFieldCollation.Direction direction =
Util.enumVal(RelFieldCollation.Direction.class,
(String) map.get("direction"));
final RelFieldCollation.NullDirection nullDirection =
Util.enumVal(RelFieldCollation.NullDirection.class,
(String) map.get("nulls"));
return new RelFieldCollation(field, direction, nullDirection);
}
public RelDataType toType(RelDataTypeFactory typeFactory, Object o) {
if (o instanceof List) {
@SuppressWarnings("unchecked")
final List<Map<String, Object>> jsonList = (List<Map<String, Object>>) o;
final RelDataTypeFactory.FieldInfoBuilder builder = typeFactory.builder();
for (Map<String, Object> jsonMap : jsonList) {
builder.add((String) jsonMap.get("name"), toType(typeFactory, jsonMap));
}
return builder.build();
} else {
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);
} else {
type = typeFactory.createSqlType(sqlTypeName, precision, scale);
}
final boolean nullable = (Boolean) map.get("nullable");
return typeFactory.createTypeWithNullability(type, nullable);
}
}
public Object toJson(AggregateCall node) {
final Map<String, Object> map = jsonBuilder.map();
map.put("agg", toJson((SqlOperator) node.getAggregation()));
map.put("type", toJson(node.getType()));
map.put("distinct", node.isDistinct());
map.put("operands", node.getArgList());
return map;
}
Object toJson(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 CorrelatorRel.Correlation) {
return toJson((CorrelatorRel.Correlation) value);
} else if (value instanceof List) {
final List<Object> list = jsonBuilder.list();
for (Object o : (List) value) {
list.add(toJson(o));
}
return list;
} else if (value instanceof BitSet) {
final List<Object> list = jsonBuilder.list();
for (Integer integer : BitSets.toIter((BitSet) 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 {
throw new UnsupportedOperationException("type not serializable: "
+ value + " (type " + value.getClass().getCanonicalName() + ")");
}
}
private Object toJson(RelDataType node) {
if (node.isStruct()) {
final List<Object> list = jsonBuilder.list();
for (RelDataTypeField field : node.getFieldList()) {
list.add(toJson(field));
}
return list;
} else {
final Map<String, Object> map = jsonBuilder.map();
map.put("type", node.getSqlTypeName().name());
map.put("nullable", node.isNullable());
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<String, Object> map =
(Map<String, Object>) toJson(node.getType());
map.put("name", node.getName());
return map;
}
private Object toJson(CorrelatorRel.Correlation node) {
final Map<String, Object> map = jsonBuilder.map();
map.put("correlation", node.getId());
map.put("offset", node.getOffset());
return map;
}
private Object toJson(RexNode node) {
final Map<String, Object> 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 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;
case INPUT_REF:
map = jsonBuilder.map();
map.put("input", ((RexInputRef) node).getIndex());
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<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()));
}
if (call.getOperator() instanceof SqlFunction) {
switch (((SqlFunction) call.getOperator()).getFunctionType()) {
case USER_DEFINED_CONSTRUCTOR:
case USER_DEFINED_FUNCTION:
case USER_DEFINED_PROCEDURE:
case USER_DEFINED_SPECIFIC_FUNCTION:
map.put("class", call.getOperator().getClass().getName());
}
}
return map;
}
throw new UnsupportedOperationException("unknown rex " + node);
}
}
RexNode toRex(RelInput relInput, Object o) {
final RelOptCluster cluster = relInput.getCluster();
final RexBuilder rexBuilder = cluster.getRexBuilder();
if (o == null) {
return null;
} else if (o instanceof Map) {
Map map = (Map) o;
final String op = (String) map.get("op");
if (op != null) {
final List operands = (List) map.get("operands");
final Object jsonType = map.get("type");
if (jsonType != null) {
RelDataType type = toType(cluster.getTypeFactory(), jsonType);
return rexBuilder.makeCall(
type, toOp(op, map), toRexList(relInput, operands));
} else {
return rexBuilder.makeCall(
toOp(op, map), toRexList(relInput, operands));
}
}
final Integer input = (Integer) map.get("input");
if (input != null) {
List<RelNode> inputNodes = relInput.getInputs();
int i = input;
for (RelNode inputNode : inputNodes) {
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");
}
final String field = (String) map.get("field");
if (field != null) {
final Object jsonExpr = map.get("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 = map.get("type");
RelDataType type = toType(cluster.getTypeFactory(), jsonType);
return rexBuilder.makeCorrel(type, correl);
}
if (map.containsKey("literal")) {
final Object literal = map.get("literal");
final SqlTypeName sqlTypeName =
Util.enumVal(SqlTypeName.class, (String) map.get("type"));
if (literal == null) {
return rexBuilder.makeNullLiteral(sqlTypeName);
}
return toRex(relInput, literal);
}
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 List<RexNode> toRexList(RelInput relInput, List operands) {
final List<RexNode> list = new ArrayList<RexNode>();
for (Object operand : operands) {
list.add(toRex(relInput, operand));
}
return list;
}
private SqlOperator toOp(String op, Map<String, Object> map) {
// TODO: build a map, for more efficient lookup
// TODO: look up based on SqlKind
final List<SqlOperator> operatorList =
SqlStdOperatorTable.instance().getOperatorList();
for (SqlOperator operator : operatorList) {
if (operator.getName().equals(op)) {
return operator;
}
}
String class_ = (String) map.get("class");
if (class_ != null) {
try {
return (SqlOperator) Class.forName(class_).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return null;
}
Aggregation toAggregation(String agg, Map<String, Object> map) {
return (Aggregation) toOp(agg, map);
}
private String toJson(SqlOperator operator) {
// User-defined operators are not yet handled.
return operator.getName();
}
}
// End RelJson.java