/* * Licensed 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.facebook.presto.hive.util; import com.facebook.presto.block.BlockSerdeUtil; import com.facebook.presto.spi.block.Block; import com.facebook.presto.spi.block.BlockBuilder; import com.facebook.presto.spi.block.BlockBuilderStatus; import com.facebook.presto.spi.block.InterleavedBlockBuilder; import com.facebook.presto.type.ArrayType; import com.facebook.presto.type.RowType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import io.airlift.slice.DynamicSliceOutput; import io.airlift.slice.Slice; import io.airlift.slice.SliceOutput; import io.airlift.slice.Slices; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; import org.apache.hadoop.io.BytesWritable; import org.joda.time.DateTime; import org.testng.annotations.Test; import java.lang.reflect.Type; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; import static com.facebook.presto.hive.HiveTestUtils.mapType; import static com.facebook.presto.hive.util.SerDeUtils.getBlockObject; import static com.facebook.presto.hive.util.SerDeUtils.serializeObject; import static com.facebook.presto.spi.type.BigintType.BIGINT; import static com.facebook.presto.spi.type.BooleanType.BOOLEAN; import static com.facebook.presto.spi.type.DoubleType.DOUBLE; import static com.facebook.presto.spi.type.IntegerType.INTEGER; import static com.facebook.presto.spi.type.RealType.REAL; import static com.facebook.presto.spi.type.SmallintType.SMALLINT; import static com.facebook.presto.spi.type.TinyintType.TINYINT; import static com.facebook.presto.spi.type.VarbinaryType.VARBINARY; import static com.facebook.presto.spi.type.VarcharType.createUnboundedVarcharType; import static com.facebook.presto.tests.StructuralTestUtil.arrayBlockOf; import static com.facebook.presto.tests.StructuralTestUtil.mapBlockOf; import static com.facebook.presto.tests.StructuralTestUtil.rowBlockOf; import static io.airlift.slice.Slices.utf8Slice; import static java.lang.Double.doubleToLongBits; import static java.lang.Float.floatToRawIntBits; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.ObjectInspectorOptions; import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getReflectionObjectInspector; import static org.testng.Assert.assertEquals; @SuppressWarnings("PackageVisibleField") public class TestSerDeUtils { private static class ListHolder { List<InnerStruct> array; } private static class InnerStruct { public InnerStruct(Integer intVal, Long longVal) { this.intVal = intVal; this.longVal = longVal; } Integer intVal; Long longVal; } private static class OuterStruct { Byte byteVal; Short shortVal; Integer intVal; Long longVal; Float floatVal; Double doubleVal; String stringVal; byte[] byteArray; List<InnerStruct> structArray; Map<String, InnerStruct> map; InnerStruct innerStruct; } private static synchronized ObjectInspector getInspector(Type type) { // ObjectInspectorFactory.getReflectionObjectInspector is not thread-safe although it // gives people a first impression that it is. This may have been fixed in HIVE-11586. // Presto only uses getReflectionObjectInspector here, in a test method. Therefore, we // choose to work around this issue by synchronizing this method. Before synchronizing // this method, test in this class fails approximately 1 out of 10 runs on Travis. return getReflectionObjectInspector(type, ObjectInspectorOptions.JAVA); } @Test public void testPrimitiveSlice() { // boolean Block expectedBoolean = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeByte(1).closeEntry().build(); Block actualBoolean = toBinaryBlock(BOOLEAN, true, getInspector(Boolean.class)); assertBlockEquals(actualBoolean, expectedBoolean); // byte Block expectedByte = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeByte(5).closeEntry().build(); Block actualByte = toBinaryBlock(TINYINT, (byte) 5, getInspector(Byte.class)); assertBlockEquals(actualByte, expectedByte); // short Block expectedShort = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeShort(2).closeEntry().build(); Block actualShort = toBinaryBlock(SMALLINT, (short) 2, getInspector(Short.class)); assertBlockEquals(actualShort, expectedShort); // int Block expectedInt = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeInt(1).closeEntry().build(); Block actualInt = toBinaryBlock(INTEGER, 1, getInspector(Integer.class)); assertBlockEquals(actualInt, expectedInt); // long Block expectedLong = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeLong(10).closeEntry().build(); Block actualLong = toBinaryBlock(BIGINT, 10L, getInspector(Long.class)); assertBlockEquals(actualLong, expectedLong); // float Block expectedFloat = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeInt(floatToRawIntBits(20.0f)).closeEntry().build(); Block actualFloat = toBinaryBlock(REAL, 20.0f, getInspector(Float.class)); assertBlockEquals(actualFloat, expectedFloat); // double Block expectedDouble = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeLong(doubleToLongBits(30.12)).closeEntry().build(); Block actualDouble = toBinaryBlock(DOUBLE, 30.12d, getInspector(Double.class)); assertBlockEquals(actualDouble, expectedDouble); // string Block expectedString = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeBytes(utf8Slice("abdd"), 0, 4).closeEntry().build(); Block actualString = toBinaryBlock(createUnboundedVarcharType(), "abdd", getInspector(String.class)); assertBlockEquals(actualString, expectedString); // timestamp DateTime dateTime = new DateTime(2008, 10, 28, 16, 7, 15, 0); Block expectedTimestamp = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeLong(dateTime.getMillis()).closeEntry().build(); Block actualTimestamp = toBinaryBlock(BIGINT, new Timestamp(dateTime.getMillis()), getInspector(Timestamp.class)); assertBlockEquals(actualTimestamp, expectedTimestamp); // binary byte[] byteArray = {81, 82, 84, 85}; Block expectedBinary = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1).writeBytes(Slices.wrappedBuffer(byteArray), 0, 4).closeEntry().build(); Block actualBinary = toBinaryBlock(createUnboundedVarcharType(), byteArray, getInspector(byte[].class)); assertBlockEquals(actualBinary, expectedBinary); } @Test public void testListBlock() { List<InnerStruct> array = new ArrayList<>(2); array.add(new InnerStruct(8, 9L)); array.add(new InnerStruct(10, 11L)); ListHolder listHolder = new ListHolder(); listHolder.array = array; com.facebook.presto.spi.type.Type rowType = new RowType(ImmutableList.of(INTEGER, BIGINT), Optional.empty()); com.facebook.presto.spi.type.Type arrayOfRowType = new RowType(ImmutableList.of(new ArrayType(rowType)), Optional.empty()); Block actual = toBinaryBlock(arrayOfRowType, listHolder, getInspector(ListHolder.class)); BlockBuilder blockBuilder = rowType.createBlockBuilder(new BlockBuilderStatus(), 1024); rowType.writeObject(blockBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 8, 9L)); rowType.writeObject(blockBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 10, 11L)); Block expected = rowBlockOf(ImmutableList.of(new ArrayType(rowType)), blockBuilder.build()); assertBlockEquals(actual, expected); } private static class MapHolder { Map<String, InnerStruct> map; } @Test public void testMapBlock() { MapHolder holder = new MapHolder(); holder.map = new TreeMap<>(); holder.map.put("twelve", new InnerStruct(13, 14L)); holder.map.put("fifteen", new InnerStruct(16, 17L)); com.facebook.presto.spi.type.Type rowType = new RowType(ImmutableList.of(INTEGER, BIGINT), Optional.empty()); com.facebook.presto.spi.type.Type mapOfVarcharRowType = new RowType(ImmutableList.of(mapType(createUnboundedVarcharType(), rowType)), Optional.empty()); Block actual = toBinaryBlock(mapOfVarcharRowType, holder, getInspector(MapHolder.class)); BlockBuilder blockBuilder = new InterleavedBlockBuilder(ImmutableList.of(createUnboundedVarcharType(), rowType), new BlockBuilderStatus(), 1024); createUnboundedVarcharType().writeString(blockBuilder, "fifteen"); rowType.writeObject(blockBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 16, 17L)); createUnboundedVarcharType().writeString(blockBuilder, "twelve"); rowType.writeObject(blockBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 13, 14L)); Block expected = rowBlockOf(ImmutableList.of(mapType(createUnboundedVarcharType(), rowType)), blockBuilder); assertBlockEquals(actual, expected); } @Test public void testStructBlock() { // test simple structs InnerStruct innerStruct = new InnerStruct(13, 14L); com.facebook.presto.spi.type.Type rowType = new RowType(ImmutableList.of(INTEGER, BIGINT), Optional.empty()); Block actual = toBinaryBlock(rowType, innerStruct, getInspector(InnerStruct.class)); Block expected = rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 13, 14L); assertBlockEquals(actual, expected); // test complex structs OuterStruct outerStruct = new OuterStruct(); outerStruct.byteVal = (byte) 1; outerStruct.shortVal = (short) 2; outerStruct.intVal = 3; outerStruct.longVal = 4L; outerStruct.floatVal = 5.01f; outerStruct.doubleVal = 6.001d; outerStruct.stringVal = "seven"; outerStruct.byteArray = new byte[] {'2'}; InnerStruct is1 = new InnerStruct(2, -5L); InnerStruct is2 = new InnerStruct(-10, 0L); outerStruct.structArray = new ArrayList<>(2); outerStruct.structArray.add(is1); outerStruct.structArray.add(is2); outerStruct.map = new TreeMap<>(); outerStruct.map.put("twelve", new InnerStruct(0, 5L)); outerStruct.map.put("fifteen", new InnerStruct(-5, -10L)); outerStruct.innerStruct = new InnerStruct(18, 19L); com.facebook.presto.spi.type.Type innerRowType = new RowType(ImmutableList.of(INTEGER, BIGINT), Optional.empty()); com.facebook.presto.spi.type.Type arrayOfInnerRowType = new ArrayType(innerRowType); com.facebook.presto.spi.type.Type mapOfInnerRowType = mapType(createUnboundedVarcharType(), innerRowType); List<com.facebook.presto.spi.type.Type> outerRowParameterTypes = ImmutableList.of(TINYINT, SMALLINT, INTEGER, BIGINT, REAL, DOUBLE, createUnboundedVarcharType(), createUnboundedVarcharType(), arrayOfInnerRowType, mapOfInnerRowType, innerRowType); com.facebook.presto.spi.type.Type outerRowType = new RowType(outerRowParameterTypes, Optional.empty()); actual = toBinaryBlock(outerRowType, outerStruct, getInspector(OuterStruct.class)); ImmutableList.Builder<Object> outerRowValues = ImmutableList.builder(); outerRowValues.add((byte) 1); outerRowValues.add((short) 2); outerRowValues.add(3); outerRowValues.add(4L); outerRowValues.add(5.01f); outerRowValues.add(6.001d); outerRowValues.add("seven"); outerRowValues.add(new byte[] {'2'}); outerRowValues.add(arrayBlockOf(innerRowType, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 2, -5L), rowBlockOf(ImmutableList.of(INTEGER, BIGINT), -10, 0L))); BlockBuilder blockBuilder = new InterleavedBlockBuilder(ImmutableList.of(createUnboundedVarcharType(), innerRowType), new BlockBuilderStatus(), 1024); createUnboundedVarcharType().writeString(blockBuilder, "fifteen"); innerRowType.writeObject(blockBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), -5, -10L)); createUnboundedVarcharType().writeString(blockBuilder, "twelve"); innerRowType.writeObject(blockBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 0, 5L)); outerRowValues.add(blockBuilder.build()); outerRowValues.add(rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 18, 19L)); assertBlockEquals(actual, rowBlockOf(outerRowParameterTypes, outerRowValues.build().toArray())); } @Test public void testReuse() throws Exception { BytesWritable value = new BytesWritable(); byte[] first = "hello world".getBytes(UTF_8); value.set(first, 0, first.length); byte[] second = "bye".getBytes(UTF_8); value.set(second, 0, second.length); Type type = new TypeToken<Map<BytesWritable, Long>>() {}.getType(); ObjectInspector inspector = getInspector(type); Block actual = getBlockObject(mapType(createUnboundedVarcharType(), BIGINT), ImmutableMap.of(value, 0L), inspector); Block expected = mapBlockOf(createUnboundedVarcharType(), BIGINT, "bye", 0L); assertBlockEquals(actual, expected); } private static void assertBlockEquals(Block actual, Block expected) { assertEquals(blockToSlice(actual), blockToSlice(expected)); } private static Slice blockToSlice(Block block) { // This function is strictly for testing use only SliceOutput sliceOutput = new DynamicSliceOutput(1000); BlockSerdeUtil.writeBlock(sliceOutput, block); return sliceOutput.slice(); } private static Block toBinaryBlock(com.facebook.presto.spi.type.Type type, Object object, ObjectInspector inspector) { if (inspector.getCategory() == Category.PRIMITIVE) { return getPrimitiveBlock(type, object, inspector); } return getBlockObject(type, object, inspector); } private static Block getPrimitiveBlock(com.facebook.presto.spi.type.Type type, Object object, ObjectInspector inspector) { BlockBuilder builder = VARBINARY.createBlockBuilder(new BlockBuilderStatus(), 1); serializeObject(type, builder, object, inspector); return builder.build(); } }