blob: 080e2c4e719c7dd4a184e68c3359cb00cedaf80e [file] [log] [blame]
/*
* 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 org.apache.asterix.lang.common.util;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.asterix.builders.OrderedListBuilder;
import org.apache.asterix.builders.RecordBuilder;
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
import org.apache.asterix.lang.common.base.Expression;
import org.apache.asterix.lang.common.base.Literal;
import org.apache.asterix.lang.common.expression.FieldBinding;
import org.apache.asterix.lang.common.expression.ListConstructor;
import org.apache.asterix.lang.common.expression.LiteralExpr;
import org.apache.asterix.lang.common.expression.RecordConstructor;
import org.apache.asterix.om.base.ABoolean;
import org.apache.asterix.om.base.ADouble;
import org.apache.asterix.om.base.AInt64;
import org.apache.asterix.om.base.AMutableString;
import org.apache.asterix.om.base.ANull;
import org.apache.asterix.om.base.AOrderedList;
import org.apache.asterix.om.base.ARecord;
import org.apache.asterix.om.base.AString;
import org.apache.asterix.om.base.IACursor;
import org.apache.asterix.om.base.IAObject;
import org.apache.asterix.om.types.BuiltinType;
import org.apache.asterix.om.utils.RecordUtil;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
import org.apache.hyracks.algebricks.common.utils.Pair;
import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
import org.apache.hyracks.api.exceptions.HyracksDataException;
import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
@SuppressWarnings("unchecked")
public class LangRecordParseUtil {
private static final String NOT_ALLOWED_EXPRESSIONS_ERROR_MESSAGE =
"JSON record can only have expressions [%1$s, %2$s, %3$s]";
private static final ISerializerDeserializer<ADouble> doubleSerde = SerializerDeserializerProvider.INSTANCE
.getSerializerDeserializer(BuiltinType.ADOUBLE);
private static final ISerializerDeserializer<AString> stringSerde = SerializerDeserializerProvider.INSTANCE
.getSerializerDeserializer(BuiltinType.ASTRING);
private static final ISerializerDeserializer<AInt64> intSerde = SerializerDeserializerProvider.INSTANCE
.getSerializerDeserializer(BuiltinType.AINT64);
private static final ISerializerDeserializer<ABoolean> booleanSerde = SerializerDeserializerProvider.INSTANCE
.getSerializerDeserializer(BuiltinType.ABOOLEAN);
private static final ISerializerDeserializer<ANull> nullSerde = SerializerDeserializerProvider.INSTANCE
.getSerializerDeserializer(BuiltinType.ANULL);
private LangRecordParseUtil() {
}
private static void parseExpression(Expression expr, ArrayBackedValueStorage serialized)
throws HyracksDataException {
switch (expr.getKind()) {
case LITERAL_EXPRESSION:
parseLiteral((LiteralExpr) expr, serialized);
break;
case RECORD_CONSTRUCTOR_EXPRESSION:
parseRecord((RecordConstructor) expr, serialized, true, Collections.emptyList());
break;
case LIST_CONSTRUCTOR_EXPRESSION:
parseList((ListConstructor) expr, serialized);
break;
default:
throw new HyracksDataException(ErrorCode.ASTERIX, ErrorCode.PARSE_ERROR,
NOT_ALLOWED_EXPRESSIONS_ERROR_MESSAGE, new Serializable[] { Expression.Kind.LITERAL_EXPRESSION
.toString(), Expression.Kind.RECORD_CONSTRUCTOR_EXPRESSION.toString(),
Expression.Kind.LIST_CONSTRUCTOR_EXPRESSION.toString() });
}
}
public static void parseRecord(RecordConstructor recordValue, ArrayBackedValueStorage serialized, boolean tagged,
List<Pair<String, String>> defaults) throws HyracksDataException {
AMutableString fieldNameString = new AMutableString(null);
ArrayBackedValueStorage fieldName = new ArrayBackedValueStorage();
ArrayBackedValueStorage fieldValue = new ArrayBackedValueStorage();
RecordBuilder recordBuilder = new RecordBuilder();
recordBuilder.reset(RecordUtil.FULLY_OPEN_RECORD_TYPE);
recordBuilder.init();
List<FieldBinding> fbList = recordValue.getFbList();
HashSet<String> fieldNames = new HashSet<>();
for (FieldBinding fb : fbList) {
fieldName.reset();
fieldValue.reset();
// get key
fieldNameString.setValue(exprToStringLiteral(fb.getLeftExpr()).getStringValue());
if (!fieldNames.add(fieldNameString.getStringValue())) {
throw new HyracksDataException("Field " + fieldNameString.getStringValue()
+ " was specified multiple times");
}
stringSerde.serialize(fieldNameString, fieldName.getDataOutput());
// get value
parseExpression(fb.getRightExpr(), fieldValue);
recordBuilder.addField(fieldName, fieldValue);
}
// defaults
for (Pair<String, String> kv : defaults) {
if (!fieldNames.contains(kv.first)) {
fieldName.reset();
fieldValue.reset();
stringSerde.serialize(new AString(kv.first), fieldName.getDataOutput());
stringSerde.serialize(new AString(kv.second), fieldValue.getDataOutput());
recordBuilder.addField(fieldName, fieldValue);
}
}
recordBuilder.write(serialized.getDataOutput(), tagged);
}
public static Literal exprToStringLiteral(Expression expr) throws HyracksDataException {
if (expr.getKind() != Expression.Kind.LITERAL_EXPRESSION) {
throw new HyracksDataException(ErrorCode.ASTERIX, ErrorCode.PARSE_ERROR,
"Expected expression can only be of type %1$s", Expression.Kind.LITERAL_EXPRESSION);
}
LiteralExpr keyLiteralExpr = (LiteralExpr) expr;
Literal keyLiteral = keyLiteralExpr.getValue();
if (keyLiteral.getLiteralType() != Literal.Type.STRING) {
throw new HyracksDataException(ErrorCode.ASTERIX, ErrorCode.PARSE_ERROR,
"Expected Literal can only be of type %1$s", Literal.Type.STRING);
}
return keyLiteral;
}
private static void parseList(ListConstructor valueExpr, ArrayBackedValueStorage serialized)
throws HyracksDataException {
if (valueExpr.getType() != ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR) {
throw new HyracksDataException(ErrorCode.ASTERIX, ErrorCode.PARSE_ERROR,
"JSON List can't be of type %1$s", valueExpr.getType());
}
ArrayBackedValueStorage serializedValue = new ArrayBackedValueStorage();
OrderedListBuilder listBuilder = new OrderedListBuilder();
listBuilder.reset(null);
for (Expression expr : valueExpr.getExprList()) {
serializedValue.reset();
parseExpression(expr, serializedValue);
listBuilder.addItem(serializedValue);
}
listBuilder.write(serialized.getDataOutput(), true);
}
private static void parseLiteral(LiteralExpr objectExpr, ArrayBackedValueStorage serialized)
throws HyracksDataException {
Literal value = objectExpr.getValue();
switch (value.getLiteralType()) {
case DOUBLE:
doubleSerde.serialize(new ADouble((Double) value.getValue()), serialized.getDataOutput());
break;
case TRUE:
booleanSerde.serialize(ABoolean.TRUE, serialized.getDataOutput());
break;
case FALSE:
booleanSerde.serialize(ABoolean.FALSE, serialized.getDataOutput());
break;
case FLOAT:
doubleSerde.serialize(new ADouble((Float) value.getValue()), serialized.getDataOutput());
break;
case INTEGER:
intSerde.serialize(new AInt64(((Integer) value.getValue()).longValue()), serialized.getDataOutput());
break;
case LONG:
intSerde.serialize(new AInt64((Long) value.getValue()), serialized.getDataOutput());
break;
case NULL:
nullSerde.serialize(ANull.NULL, serialized.getDataOutput());
break;
case STRING:
stringSerde.serialize(new AString((String) value.getValue()), serialized.getDataOutput());
break;
default:
throw new HyracksDataException(ErrorCode.ASTERIX, ErrorCode.PARSE_ERROR,
"Unknown Literal Type %1$s", value.getLiteralType());
}
}
public static void recordToMap(Map<String, String> map, ARecord record)
throws AlgebricksException {
String[] keys = record.getType().getFieldNames();
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
String value = aObjToString(record.getValueByPos(i));
map.put(key, value);
}
}
public static String aObjToString(IAObject aObj) throws AlgebricksException {
switch (aObj.getType().getTypeTag()) {
case DOUBLE:
return Double.toString(((ADouble) aObj).getDoubleValue());
case BIGINT:
return Long.toString(((AInt64) aObj).getLongValue());
case ARRAY:
return aOrderedListToString((AOrderedList) aObj);
case STRING:
return ((AString) aObj).getStringValue();
default:
throw new AlgebricksException("value of type " + aObj.getType() + " is not supported yet");
}
}
private static String aOrderedListToString(AOrderedList ol) throws AlgebricksException {
StringBuilder delimitedList = new StringBuilder();
IACursor cursor = ol.getCursor();
if (cursor.next()) {
IAObject next = cursor.get();
delimitedList.append(aObjToString(next));
}
while (cursor.next()) {
IAObject next = cursor.get();
delimitedList.append(",");
delimitedList.append(aObjToString(next));
}
return delimitedList.toString();
}
}