/*
* 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.type;
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.type.Type;
import com.google.common.collect.ImmutableMap;
import io.airlift.slice.DynamicSliceOutput;
import io.airlift.slice.Slice;
import io.airlift.slice.SliceOutput;
import io.airlift.slice.Slices;
import org.testng.annotations.Test;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import static com.facebook.presto.block.BlockSerdeUtil.writeBlock;
import static com.facebook.presto.operator.OperatorAssertion.toRow;
import static com.facebook.presto.spi.block.SortOrder.ASC_NULLS_FIRST;
import static com.facebook.presto.spi.block.SortOrder.ASC_NULLS_LAST;
import static com.facebook.presto.spi.block.SortOrder.DESC_NULLS_FIRST;
import static com.facebook.presto.spi.block.SortOrder.DESC_NULLS_LAST;
import static com.facebook.presto.testing.TestingConnectorSession.SESSION;
import static com.facebook.presto.type.TypeUtils.hashPosition;
import static com.facebook.presto.type.TypeUtils.positionEqualsPosition;
import static com.facebook.presto.util.StructuralTestUtil.arrayBlockOf;
import static com.facebook.presto.util.StructuralTestUtil.mapBlockOf;
import static io.airlift.testing.Assertions.assertInstanceOf;
import static java.util.Collections.unmodifiableSortedMap;
import static java.util.Objects.requireNonNull;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
public abstract class AbstractTestType
{
private final Class<?> objectValueType;
private final Block testBlock;
private final Type type;
private final SortedMap<Integer, Object> expectedStackValues;
private final SortedMap<Integer, Object> expectedObjectValues;
private final Block testBlockWithNulls;
protected AbstractTestType(Type type, Class<?> objectValueType, Block testBlock)
{
this(type, objectValueType, testBlock, testBlock);
}
protected AbstractTestType(Type type, Class<?> objectValueType, Block testBlock, Block expectedValues)
{
this.type = requireNonNull(type, "type is null");
this.objectValueType = requireNonNull(objectValueType, "objectValueType is null");
this.testBlock = requireNonNull(testBlock, "testBlock is null");
requireNonNull(expectedValues, "expectedValues is null");
this.expectedStackValues = indexStackValues(type, expectedValues);
this.expectedObjectValues = indexObjectValues(type, expectedValues);
this.testBlockWithNulls = createAlternatingNullsBlock(testBlock);
}
private Block createAlternatingNullsBlock(Block testBlock)
{
BlockBuilder nullsBlockBuilder = type.createBlockBuilder(new BlockBuilderStatus(), testBlock.getPositionCount());
for (int position = 0; position < testBlock.getPositionCount(); position++) {
if (type.getJavaType() == void.class) {
nullsBlockBuilder.appendNull();
}
else if (type.getJavaType() == boolean.class) {
type.writeBoolean(nullsBlockBuilder, type.getBoolean(testBlock, position));
}
else if (type.getJavaType() == long.class) {
type.writeLong(nullsBlockBuilder, type.getLong(testBlock, position));
}
else if (type.getJavaType() == double.class) {
type.writeDouble(nullsBlockBuilder, type.getDouble(testBlock, position));
}
else if (type.getJavaType() == Slice.class) {
Slice slice = type.getSlice(testBlock, position);
type.writeSlice(nullsBlockBuilder, slice, 0, slice.length());
}
else {
type.writeObject(nullsBlockBuilder, type.getObject(testBlock, position));
}
nullsBlockBuilder.appendNull();
}
return nullsBlockBuilder.build();
}
@Test
public void testBlock()
{
for (Entry<Integer, Object> entry : expectedStackValues.entrySet()) {
assertPositionEquals(testBlock, entry.getKey(), entry.getValue(), expectedObjectValues.get(entry.getKey()));
}
for (Entry<Integer, Object> entry : expectedStackValues.entrySet()) {
assertPositionEquals(testBlockWithNulls, entry.getKey() * 2, entry.getValue(), expectedObjectValues.get(entry.getKey()));
assertPositionEquals(testBlockWithNulls, (entry.getKey() * 2) + 1, null, null);
}
}
protected void assertPositionEquals(Block block, int position, Object expectedStackValue, Object expectedObjectValue)
{
long hash = 0;
if (type.isComparable()) {
hash = hashPosition(type, block, position);
}
assertPositionValue(block, position, expectedStackValue, hash, expectedObjectValue);
assertPositionValue(block.getSingleValueBlock(position), 0, expectedStackValue, hash, expectedObjectValue);
assertPositionValue(block.getRegion(position, 1), 0, expectedStackValue, hash, expectedObjectValue);
assertPositionValue(block.getRegion(0, position + 1), position, expectedStackValue, hash, expectedObjectValue);
assertPositionValue(block.getRegion(position, block.getPositionCount() - position), 0, expectedStackValue, hash, expectedObjectValue);
BlockBuilder blockBuilder = type.createBlockBuilder(new BlockBuilderStatus(), 1);
type.appendTo(block, position, blockBuilder);
assertPositionValue(blockBuilder.build(), 0, expectedStackValue, hash, expectedObjectValue);
}
private void assertPositionValue(Block block, int position, Object expectedStackValue, long expectedHash, Object expectedObjectValue)
{
Object objectValue = type.getObjectValue(SESSION, block, position);
assertEquals(objectValue, expectedObjectValue);
if (objectValue != null) {
assertInstanceOf(objectValue, objectValueType);
}
if (type.isComparable()) {
assertEquals(hashPosition(type, block, position), expectedHash);
}
else {
try {
type.hash(block, position);
fail("Expected UnsupportedOperationException");
}
catch (UnsupportedOperationException expected) {
}
}
Block expectedBlock = createBlock(type, expectedStackValue);
if (type.isComparable()) {
assertTrue(positionEqualsPosition(type, block, position, block, position));
assertTrue(positionEqualsPosition(type, block, position, expectedBlock, 0));
assertTrue(positionEqualsPosition(type, expectedBlock, 0, block, position));
}
assertEquals(block.isNull(position), expectedStackValue == null);
if (type.isOrderable()) {
assertTrue(ASC_NULLS_FIRST.compareBlockValue(type, block, position, expectedBlock, 0) == 0);
assertTrue(ASC_NULLS_LAST.compareBlockValue(type, block, position, expectedBlock, 0) == 0);
assertTrue(DESC_NULLS_FIRST.compareBlockValue(type, block, position, expectedBlock, 0) == 0);
assertTrue(DESC_NULLS_LAST.compareBlockValue(type, block, position, expectedBlock, 0) == 0);
}
else {
try {
type.compareTo(block, position, expectedBlock, 0);
fail("Expected UnsupportedOperationException");
}
catch (UnsupportedOperationException expected) {
}
}
verifyInvalidPositionHandling(block);
if (block.isNull(position)) {
if (type.isOrderable() && type.getJavaType() != void.class) {
Block nonNullValue = toBlock(getNonNullValue());
assertTrue(ASC_NULLS_FIRST.compareBlockValue(type, block, position, nonNullValue, 0) < 0);
assertTrue(ASC_NULLS_LAST.compareBlockValue(type, block, position, nonNullValue, 0) > 0);
assertTrue(DESC_NULLS_FIRST.compareBlockValue(type, block, position, nonNullValue, 0) < 0);
assertTrue(DESC_NULLS_LAST.compareBlockValue(type, block, position, nonNullValue, 0) > 0);
}
return;
}
if (type.isOrderable() && expectedStackValue != Boolean.TRUE) {
Block greaterValue = toBlock(getGreaterValue(expectedStackValue));
assertTrue(ASC_NULLS_FIRST.compareBlockValue(type, block, position, greaterValue, 0) < 0);
assertTrue(ASC_NULLS_LAST.compareBlockValue(type, block, position, greaterValue, 0) < 0);
assertTrue(DESC_NULLS_FIRST.compareBlockValue(type, block, position, greaterValue, 0) > 0);
assertTrue(DESC_NULLS_LAST.compareBlockValue(type, block, position, greaterValue, 0) > 0);
}
if (type.getJavaType() == boolean.class) {
assertEquals(type.getBoolean(block, position), expectedStackValue);
try {
type.getLong(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getDouble(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getObject(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
}
else if (type.getJavaType() == long.class) {
assertEquals(type.getLong(block, position), expectedStackValue);
try {
type.getBoolean(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getDouble(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getObject(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
}
else if (type.getJavaType() == double.class) {
assertEquals(type.getDouble(block, position), expectedStackValue);
try {
type.getBoolean(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getLong(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getObject(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
}
else if (type.getJavaType() == Slice.class) {
assertEquals(type.getSlice(block, position), expectedStackValue);
try {
type.getBoolean(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getLong(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getDouble(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getObject(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
}
else {
SliceOutput actualSliceOutput = new DynamicSliceOutput(100);
writeBlock(actualSliceOutput, (Block) type.getObject(block, position));
SliceOutput expectedSliceOutput = new DynamicSliceOutput(actualSliceOutput.size());
writeBlock(expectedSliceOutput, (Block) expectedStackValue);
assertEquals(actualSliceOutput.slice(), expectedSliceOutput.slice());
try {
type.getBoolean(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getLong(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getDouble(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
try {
type.getSlice(block, position);
fail("Expected IllegalStateException or UnsupportedOperationException");
}
catch (IllegalStateException | UnsupportedOperationException expected) {
}
}
}
private void verifyInvalidPositionHandling(Block block)
{
try {
type.getObjectValue(SESSION, block, -1);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.getObjectValue(SESSION, block, block.getPositionCount());
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.hash(block, -1);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.hash(block, block.getPositionCount());
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
if (type.isComparable() && type.getJavaType() != void.class) {
Block other = toBlock(getNonNullValue());
try {
type.equalTo(block, -1, other, 0);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.equalTo(block, block.getPositionCount(), other, 0);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
}
if (type.isOrderable() && type.getJavaType() != void.class) {
Block other = toBlock(getNonNullValue());
try {
ASC_NULLS_FIRST.compareBlockValue(type, block, -1, other, 0);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
ASC_NULLS_FIRST.compareBlockValue(type, block, block.getPositionCount(), other, 0);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
}
if (type.getJavaType() == boolean.class) {
try {
type.getBoolean(block, -1);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.getBoolean(block, block.getPositionCount());
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
}
else if (type.getJavaType() == long.class) {
try {
type.getLong(block, -1);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.getLong(block, block.getPositionCount());
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
}
else if (type.getJavaType() == double.class) {
try {
type.getDouble(block, -1);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.getDouble(block, block.getPositionCount());
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
}
else if (type.getJavaType() == Slice.class) {
try {
type.getSlice(block, -1);
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
try {
type.getSlice(block, block.getPositionCount());
fail("expected RuntimeException");
}
catch (RuntimeException expected) {
}
}
}
private static Block createBlock(Type type, Object value)
{
BlockBuilder blockBuilder = type.createBlockBuilder(new BlockBuilderStatus(), 1);
Class<?> javaType = type.getJavaType();
if (value == null) {
blockBuilder.appendNull();
}
else if (javaType == boolean.class) {
type.writeBoolean(blockBuilder, (Boolean) value);
}
else if (javaType == long.class) {
type.writeLong(blockBuilder, (Long) value);
}
else if (javaType == double.class) {
type.writeDouble(blockBuilder, (Double) value);
}
else if (javaType == Slice.class) {
Slice slice = (Slice) value;
type.writeSlice(blockBuilder, slice, 0, slice.length());
}
else {
type.writeObject(blockBuilder, value);
}
return blockBuilder.build();
}
protected abstract Object getGreaterValue(Object value);
protected Object getNonNullValue()
{
return getNonNullValueForType(type);
}
private static Object getNonNullValueForType(Type type)
{
if (type.getJavaType() == boolean.class) {
return true;
}
if (type.getJavaType() == long.class) {
return 1L;
}
if (type.getJavaType() == double.class) {
return 1.0;
}
if (type.getJavaType() == Slice.class) {
return Slices.utf8Slice("_");
}
if (type instanceof ArrayType) {
ArrayType arrayType = (ArrayType) type;
Type elementType = arrayType.getElementType();
Object elementNonNullValue = getNonNullValueForType(elementType);
return arrayBlockOf(elementType, elementNonNullValue);
}
if (type instanceof MapType) {
MapType mapType = (MapType) type;
Type keyType = mapType.getKeyType();
Type valueType = mapType.getValueType();
Object keyNonNullValue = getNonNullValueForType(keyType);
Object valueNonNullValue = getNonNullValueForType(valueType);
Map map = ImmutableMap.of(keyNonNullValue, valueNonNullValue);
return mapBlockOf(keyType, valueType, map);
}
if (type instanceof RowType) {
RowType rowType = (RowType) type;
List<Type> elementTypes = rowType.getTypeParameters();
Object[] elementNonNullValues = elementTypes.stream().map(AbstractTestType::getNonNullValueForType).toArray(Object[]::new);
return toRow(elementTypes, elementNonNullValues);
}
throw new IllegalStateException("Unsupported Java type " + type.getJavaType() + " (for type " + type + ")");
}
private Block toBlock(Object value)
{
BlockBuilder blockBuilder = type.createBlockBuilder(new BlockBuilderStatus(), 1);
Class<?> javaType = type.getJavaType();
if (value == null) {
blockBuilder.appendNull();
}
else if (javaType == boolean.class) {
type.writeBoolean(blockBuilder, (Boolean) value);
}
else if (javaType == long.class) {
type.writeLong(blockBuilder, (Long) value);
}
else if (javaType == double.class) {
type.writeDouble(blockBuilder, (Double) value);
}
else if (javaType == Slice.class) {
Slice slice = (Slice) value;
type.writeSlice(blockBuilder, slice, 0, slice.length());
}
else {
type.writeObject(blockBuilder, value);
}
return blockBuilder.build();
}
private static SortedMap<Integer, Object> indexStackValues(Type type, Block block)
{
SortedMap<Integer, Object> values = new TreeMap<>();
for (int position = 0; position < block.getPositionCount(); position++) {
if (block.isNull(position)) {
values.put(position, null);
}
else if (type.getJavaType() == boolean.class) {
values.put(position, type.getBoolean(block, position));
}
else if (type.getJavaType() == long.class) {
values.put(position, type.getLong(block, position));
}
else if (type.getJavaType() == double.class) {
values.put(position, type.getDouble(block, position));
}
else if (type.getJavaType() == Slice.class) {
values.put(position, type.getSlice(block, position));
}
else {
values.put(position, type.getObject(block, position));
}
}
return unmodifiableSortedMap(values);
}
private static SortedMap<Integer, Object> indexObjectValues(Type type, Block block)
{
SortedMap<Integer, Object> values = new TreeMap<>();
for (int position = 0; position < block.getPositionCount(); position++) {
values.put(position, type.getObjectValue(SESSION, block, position));
}
return unmodifiableSortedMap(values);
}
}