/**
* 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.hadoop.hive.serde2.lazy;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.serde.Constants;
import org.apache.hadoop.hive.serde2.ByteStream;
import org.apache.hadoop.hive.serde2.SerDe;
import org.apache.hadoop.hive.serde2.SerDeException;
import org.apache.hadoop.hive.serde2.SerDeStats;
import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StructField;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.UnionObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.typeinfo.StructTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
/**
* LazySimpleSerDe can be used to read the same data format as
* MetadataTypedColumnsetSerDe and TCTLSeparatedProtocol.
*
* However, LazySimpleSerDe creates Objects in a lazy way, to provide better
* performance.
*
* Also LazySimpleSerDe outputs typed columns instead of treating all columns as
* String like MetadataTypedColumnsetSerDe.
*/
public class LazySimpleSerDe implements SerDe {
public static final Log LOG = LogFactory.getLog(LazySimpleSerDe.class
.getName());
public static final byte[] DefaultSeparators = {(byte) 1, (byte) 2, (byte) 3};
private ObjectInspector cachedObjectInspector;
private long serializedSize;
private SerDeStats stats;
private boolean lastOperationSerialize;
private boolean lastOperationDeserialize;
@Override
public String toString() {
return getClass().toString()
+ "["
+ Arrays.asList(serdeParams.separators)
+ ":"
+ ((StructTypeInfo) serdeParams.rowTypeInfo).getAllStructFieldNames()
+ ":"
+ ((StructTypeInfo) serdeParams.rowTypeInfo)
.getAllStructFieldTypeInfos() + "]";
}
public LazySimpleSerDe() throws SerDeException {
}
/**
* Return the byte value of the number string.
*
* @param altValue
* The string containing a number.
* @param defaultVal
* If the altValue does not represent a number, return the
* defaultVal.
*/
public static byte getByte(String altValue, byte defaultVal) {
if (altValue != null && altValue.length() > 0) {
try {
return Byte.valueOf(altValue).byteValue();
} catch (NumberFormatException e) {
return (byte) altValue.charAt(0);
}
}
return defaultVal;
}
/**
* SerDeParameters.
*
*/
public static class SerDeParameters {
byte[] separators = DefaultSeparators;
String nullString;
Text nullSequence;
TypeInfo rowTypeInfo;
boolean lastColumnTakesRest;
List<String> columnNames;
List<TypeInfo> columnTypes;
boolean escaped;
byte escapeChar;
boolean[] needsEscape;
public List<TypeInfo> getColumnTypes() {
return columnTypes;
}
public List<String> getColumnNames() {
return columnNames;
}
public byte[] getSeparators() {
return separators;
}
public String getNullString() {
return nullString;
}
public Text getNullSequence() {
return nullSequence;
}
public TypeInfo getRowTypeInfo() {
return rowTypeInfo;
}
public boolean isLastColumnTakesRest() {
return lastColumnTakesRest;
}
public boolean isEscaped() {
return escaped;
}
public byte getEscapeChar() {
return escapeChar;
}
public boolean[] getNeedsEscape() {
return needsEscape;
}
}
SerDeParameters serdeParams = null;
/**
* Initialize the SerDe given the parameters. serialization.format: separator
* char or byte code (only supports byte-value up to 127) columns:
* ","-separated column names columns.types: ",", ":", or ";"-separated column
* types
*
* @see SerDe#initialize(Configuration, Properties)
*/
public void initialize(Configuration job, Properties tbl)
throws SerDeException {
serdeParams = LazySimpleSerDe.initSerdeParams(job, tbl, getClass()
.getName());
// Create the ObjectInspectors for the fields
cachedObjectInspector = LazyFactory.createLazyStructInspector(serdeParams
.getColumnNames(), serdeParams.getColumnTypes(), serdeParams
.getSeparators(), serdeParams.getNullSequence(), serdeParams
.isLastColumnTakesRest(), serdeParams.isEscaped(), serdeParams
.getEscapeChar());
cachedLazyStruct = (LazyStruct) LazyFactory
.createLazyObject(cachedObjectInspector);
LOG.debug(getClass().getName() + " initialized with: columnNames="
+ serdeParams.columnNames + " columnTypes=" + serdeParams.columnTypes
+ " separator=" + Arrays.asList(serdeParams.separators)
+ " nullstring=" + serdeParams.nullString + " lastColumnTakesRest="
+ serdeParams.lastColumnTakesRest);
serializedSize = 0;
stats = new SerDeStats();
lastOperationSerialize = false;
lastOperationDeserialize = false;
}
public static SerDeParameters initSerdeParams(Configuration job,
Properties tbl, String serdeName) throws SerDeException {
SerDeParameters serdeParams = new SerDeParameters();
// Read the separators: We use 8 levels of separators by default, but we
// should change this when we allow users to specify more than 10 levels
// of separators through DDL.
serdeParams.separators = new byte[8];
serdeParams.separators[0] = getByte(tbl.getProperty(Constants.FIELD_DELIM,
tbl.getProperty(Constants.SERIALIZATION_FORMAT)), DefaultSeparators[0]);
serdeParams.separators[1] = getByte(tbl
.getProperty(Constants.COLLECTION_DELIM), DefaultSeparators[1]);
serdeParams.separators[2] = getByte(
tbl.getProperty(Constants.MAPKEY_DELIM), DefaultSeparators[2]);
for (int i = 3; i < serdeParams.separators.length; i++) {
serdeParams.separators[i] = (byte) (i + 1);
}
serdeParams.nullString = tbl.getProperty(
Constants.SERIALIZATION_NULL_FORMAT, "\\N");
serdeParams.nullSequence = new Text(serdeParams.nullString);
String lastColumnTakesRestString = tbl
.getProperty(Constants.SERIALIZATION_LAST_COLUMN_TAKES_REST);
serdeParams.lastColumnTakesRest = (lastColumnTakesRestString != null && lastColumnTakesRestString
.equalsIgnoreCase("true"));
LazyUtils.extractColumnInfo(tbl, serdeParams, serdeName);
// Create the LazyObject for storing the rows
serdeParams.rowTypeInfo = TypeInfoFactory.getStructTypeInfo(
serdeParams.columnNames, serdeParams.columnTypes);
// Get the escape information
String escapeProperty = tbl.getProperty(Constants.ESCAPE_CHAR);
serdeParams.escaped = (escapeProperty != null);
if (serdeParams.escaped) {
serdeParams.escapeChar = getByte(escapeProperty, (byte) '\\');
}
if (serdeParams.escaped) {
serdeParams.needsEscape = new boolean[128];
for (int i = 0; i < 128; i++) {
serdeParams.needsEscape[i] = false;
}
serdeParams.needsEscape[serdeParams.escapeChar] = true;
for (int i = 0; i < serdeParams.separators.length; i++) {
serdeParams.needsEscape[serdeParams.separators[i]] = true;
}
}
return serdeParams;
}
// The object for storing row data
LazyStruct cachedLazyStruct;
// The wrapper for byte array
ByteArrayRef byteArrayRef;
/**
* Deserialize a row from the Writable to a LazyObject.
*
* @param field
* the Writable that contains the data
* @return The deserialized row Object.
* @see SerDe#deserialize(Writable)
*/
public Object deserialize(Writable field) throws SerDeException {
if (byteArrayRef == null) {
byteArrayRef = new ByteArrayRef();
}
if (field instanceof BytesWritable) {
BytesWritable b = (BytesWritable) field;
// For backward-compatibility with hadoop 0.17
byteArrayRef.setData(b.getBytes());
cachedLazyStruct.init(byteArrayRef, 0, b.getLength());
} else if (field instanceof Text) {
Text t = (Text) field;
byteArrayRef.setData(t.getBytes());
cachedLazyStruct.init(byteArrayRef, 0, t.getLength());
} else {
throw new SerDeException(getClass().toString()
+ ": expects either BytesWritable or Text object!");
}
lastOperationSerialize = false;
lastOperationDeserialize = true;
return cachedLazyStruct;
}
/**
* Returns the ObjectInspector for the row.
*/
public ObjectInspector getObjectInspector() throws SerDeException {
return cachedObjectInspector;
}
/**
* Returns the Writable Class after serialization.
*
* @see SerDe#getSerializedClass()
*/
public Class<? extends Writable> getSerializedClass() {
return Text.class;
}
Text serializeCache = new Text();
ByteStream.Output serializeStream = new ByteStream.Output();
/**
* Serialize a row of data.
*
* @param obj
* The row object
* @param objInspector
* The ObjectInspector for the row object
* @return The serialized Writable object
* @throws IOException
* @see SerDe#serialize(Object, ObjectInspector)
*/
public Writable serialize(Object obj, ObjectInspector objInspector)
throws SerDeException {
if (objInspector.getCategory() != Category.STRUCT) {
throw new SerDeException(getClass().toString()
+ " can only serialize struct types, but we got: "
+ objInspector.getTypeName());
}
// Prepare the field ObjectInspectors
StructObjectInspector soi = (StructObjectInspector) objInspector;
List<? extends StructField> fields = soi.getAllStructFieldRefs();
List<Object> list = soi.getStructFieldsDataAsList(obj);
List<? extends StructField> declaredFields = (serdeParams.rowTypeInfo != null && ((StructTypeInfo) serdeParams.rowTypeInfo)
.getAllStructFieldNames().size() > 0) ? ((StructObjectInspector) getObjectInspector())
.getAllStructFieldRefs()
: null;
serializeStream.reset();
serializedSize = 0;
// Serialize each field
for (int i = 0; i < fields.size(); i++) {
// Append the separator if needed.
if (i > 0) {
serializeStream.write(serdeParams.separators[0]);
}
// Get the field objectInspector and the field object.
ObjectInspector foi = fields.get(i).getFieldObjectInspector();
Object f = (list == null ? null : list.get(i));
if (declaredFields != null && i >= declaredFields.size()) {
throw new SerDeException("Error: expecting " + declaredFields.size()
+ " but asking for field " + i + "\n" + "data=" + obj + "\n"
+ "tableType=" + serdeParams.rowTypeInfo.toString() + "\n"
+ "dataType="
+ TypeInfoUtils.getTypeInfoFromObjectInspector(objInspector));
}
serializeField(serializeStream, f, foi, serdeParams);
}
// TODO: The copy of data is unnecessary, but there is no work-around
// since we cannot directly set the private byte[] field inside Text.
serializeCache
.set(serializeStream.getData(), 0, serializeStream.getCount());
serializedSize = serializeStream.getCount();
lastOperationSerialize = true;
lastOperationDeserialize = false;
return serializeCache;
}
protected void serializeField(ByteStream.Output out, Object obj, ObjectInspector objInspector,
SerDeParameters serdeParams) throws SerDeException {
try {
serialize(out, obj, objInspector, serdeParams.separators, 1, serdeParams.nullSequence,
serdeParams.escaped, serdeParams.escapeChar, serdeParams.needsEscape);
} catch (IOException e) {
throw new SerDeException(e);
}
}
/**
* Serialize the row into the StringBuilder.
*
* @param out
* The StringBuilder to store the serialized data.
* @param obj
* The object for the current field.
* @param objInspector
* The ObjectInspector for the current Object.
* @param separators
* The separators array.
* @param level
* The current level of separator.
* @param nullSequence
* The byte sequence representing the NULL value.
* @param escaped
* Whether we need to escape the data when writing out
* @param escapeChar
* Which char to use as the escape char, e.g. '\\'
* @param needsEscape
* Which chars needs to be escaped. This array should have size of
* 128. Negative byte values (or byte values >= 128) are never
* escaped.
* @throws IOException
*/
public static void serialize(ByteStream.Output out, Object obj,
ObjectInspector objInspector, byte[] separators, int level,
Text nullSequence, boolean escaped, byte escapeChar, boolean[] needsEscape)
throws IOException {
if (obj == null) {
out.write(nullSequence.getBytes(), 0, nullSequence.getLength());
return;
}
char separator;
List<?> list;
switch (objInspector.getCategory()) {
case PRIMITIVE:
LazyUtils.writePrimitiveUTF8(out, obj,
(PrimitiveObjectInspector) objInspector, escaped, escapeChar,
needsEscape);
return;
case LIST:
separator = (char) separators[level];
ListObjectInspector loi = (ListObjectInspector) objInspector;
list = loi.getList(obj);
ObjectInspector eoi = loi.getListElementObjectInspector();
if (list == null) {
out.write(nullSequence.getBytes(), 0, nullSequence.getLength());
} else {
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
out.write(separator);
}
serialize(out, list.get(i), eoi, separators, level + 1, nullSequence,
escaped, escapeChar, needsEscape);
}
}
return;
case MAP:
separator = (char) separators[level];
char keyValueSeparator = (char) separators[level + 1];
MapObjectInspector moi = (MapObjectInspector) objInspector;
ObjectInspector koi = moi.getMapKeyObjectInspector();
ObjectInspector voi = moi.getMapValueObjectInspector();
Map<?, ?> map = moi.getMap(obj);
if (map == null) {
out.write(nullSequence.getBytes(), 0, nullSequence.getLength());
} else {
boolean first = true;
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (first) {
first = false;
} else {
out.write(separator);
}
serialize(out, entry.getKey(), koi, separators, level + 2,
nullSequence, escaped, escapeChar, needsEscape);
out.write(keyValueSeparator);
serialize(out, entry.getValue(), voi, separators, level + 2,
nullSequence, escaped, escapeChar, needsEscape);
}
}
return;
case STRUCT:
separator = (char) separators[level];
StructObjectInspector soi = (StructObjectInspector) objInspector;
List<? extends StructField> fields = soi.getAllStructFieldRefs();
list = soi.getStructFieldsDataAsList(obj);
if (list == null) {
out.write(nullSequence.getBytes(), 0, nullSequence.getLength());
} else {
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
out.write(separator);
}
serialize(out, list.get(i), fields.get(i).getFieldObjectInspector(),
separators, level + 1, nullSequence, escaped, escapeChar,
needsEscape);
}
}
return;
case UNION:
separator = (char) separators[level];
UnionObjectInspector uoi = (UnionObjectInspector) objInspector;
List<? extends ObjectInspector> ois = uoi.getObjectInspectors();
if (ois == null) {
out.write(nullSequence.getBytes(), 0, nullSequence.getLength());
} else {
LazyUtils.writePrimitiveUTF8(out, new Byte(uoi.getTag(obj)),
PrimitiveObjectInspectorFactory.javaByteObjectInspector,
escaped, escapeChar, needsEscape);
out.write(separator);
serialize(out, uoi.getField(obj), ois.get(uoi.getTag(obj)),
separators, level + 1, nullSequence, escaped, escapeChar,
needsEscape);
}
return;
default:
break;
}
throw new RuntimeException("Unknown category type: "
+ objInspector.getCategory());
}
/**
* Returns the statistics after (de)serialization)
*/
public SerDeStats getSerDeStats() {
// must be different
assert (lastOperationSerialize != lastOperationDeserialize);
if (lastOperationSerialize) {
stats.setRawDataSize(serializedSize);
} else {
stats.setRawDataSize(cachedLazyStruct.getRawDataSerializedSize());
}
return stats;
}
}