/*
* 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.operator.project;
import com.facebook.presto.spi.ConnectorSession;
import com.facebook.presto.spi.Page;
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.DictionaryBlock;
import com.facebook.presto.spi.block.LazyBlock;
import com.facebook.presto.spi.block.LongArrayBlock;
import com.facebook.presto.spi.block.RunLengthEncodedBlock;
import com.facebook.presto.spi.type.Type;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.testng.annotations.Test;
import java.util.Arrays;
import static com.facebook.presto.block.BlockAssertions.assertBlockEquals;
import static com.facebook.presto.block.BlockAssertions.createLongSequenceBlock;
import static com.facebook.presto.spi.block.DictionaryId.randomDictionaryId;
import static com.facebook.presto.spi.type.BigintType.BIGINT;
import static io.airlift.testing.Assertions.assertInstanceOf;
import static org.testng.Assert.assertEquals;
public class TestDictionaryAwarePageProjection
{
@Test
public void testDelegateMethods()
throws Exception
{
DictionaryAwarePageProjection projection = createProjection();
assertEquals(projection.isDeterministic(), true);
assertEquals(projection.getInputChannels().getInputChannels(), ImmutableList.of(3));
assertEquals(projection.getType(), BIGINT);
}
@Test
public void testSimpleBlock()
throws Exception
{
Block block = createLongSequenceBlock(0, 100);
testProject(block, block.getClass());
}
@Test
public void testRleBlock()
throws Exception
{
Block value = createLongSequenceBlock(42, 43);
RunLengthEncodedBlock block = new RunLengthEncodedBlock(value, 100);
testProject(block, RunLengthEncodedBlock.class);
}
@Test
public void testDictionaryBlock()
throws Exception
{
DictionaryBlock block = createDictionaryBlock(10, 100);
testProject(block, DictionaryBlock.class);
}
@Test
public void testDictionaryBlockProcessingWithUnusedFailure()
throws Exception
{
DictionaryBlock block = createDictionaryBlockWithUnusedEntries(10, 100);
// failures in the dictionary processing will cause a fallback to normal columnar processing
testProject(block, LongArrayBlock.class);
}
@Test
public void testDictionaryProcessingEnableDisable()
throws Exception
{
DictionaryAwarePageProjection projection = createProjection();
// function will always processes the first dictionary
DictionaryBlock ineffectiveBlock = createDictionaryBlock(100, 20);
testProjectRange(ineffectiveBlock, DictionaryBlock.class, projection);
testProjectList(ineffectiveBlock, DictionaryBlock.class, projection);
// last dictionary not effective, so dictionary processing is disabled
DictionaryBlock effectiveBlock = createDictionaryBlock(10, 100);
testProjectRange(effectiveBlock, LongArrayBlock.class, projection);
testProjectList(effectiveBlock, LongArrayBlock.class, projection);
// last dictionary not effective, so dictionary processing is enabled again
testProjectRange(ineffectiveBlock, DictionaryBlock.class, projection);
testProjectList(ineffectiveBlock, DictionaryBlock.class, projection);
// last dictionary not effective, so dictionary processing is disabled again
testProjectRange(effectiveBlock, LongArrayBlock.class, projection);
testProjectList(effectiveBlock, LongArrayBlock.class, projection);
}
private static DictionaryBlock createDictionaryBlock(int dictionarySize, int blockSize)
{
Block dictionary = createLongSequenceBlock(0, dictionarySize);
int[] ids = new int[blockSize];
Arrays.setAll(ids, index -> index % dictionarySize);
return new DictionaryBlock(blockSize, dictionary, ids);
}
private static DictionaryBlock createDictionaryBlockWithUnusedEntries(int dictionarySize, int blockSize)
{
Block dictionary = createLongSequenceBlock(-10, dictionarySize);
int[] ids = new int[blockSize];
Arrays.setAll(ids, index -> (index % dictionarySize) + 10);
return new DictionaryBlock(blockSize, dictionary, ids);
}
private static void testProject(Block block, Class<? extends Block> expectedResultType)
{
testProjectRange(block, expectedResultType, createProjection());
testProjectList(block, expectedResultType, createProjection());
testProjectRange(lazyWrapper(block), expectedResultType, createProjection());
testProjectList(lazyWrapper(block), expectedResultType, createProjection());
}
private static void testProjectRange(Block block, Class<? extends Block> expectedResultType, DictionaryAwarePageProjection projection)
{
Block result = projection.project(null, new Page(block), SelectedPositions.positionsRange(5, 10));
assertBlockEquals(
BIGINT,
result,
block.getRegion(5, 10));
assertInstanceOf(result, expectedResultType);
}
private static void testProjectList(Block block, Class<? extends Block> expectedResultType, DictionaryAwarePageProjection projection)
{
int[] positions = {0, 2, 4, 6, 8, 10};
Block result = projection.project(null, new Page(block), SelectedPositions.positionsList(positions, 0, positions.length));
assertBlockEquals(
BIGINT,
result,
block.copyPositions(new IntArrayList(positions)));
assertInstanceOf(result, expectedResultType);
}
private static DictionaryAwarePageProjection createProjection()
{
return new DictionaryAwarePageProjection(
new TestPageProjection(),
block -> randomDictionaryId());
}
private static LazyBlock lazyWrapper(Block block)
{
return new LazyBlock(block.getPositionCount(), lazyBlock -> lazyBlock.setBlock(block));
}
private static class TestPageProjection
implements PageProjection
{
@Override
public Type getType()
{
return BIGINT;
}
@Override
public boolean isDeterministic()
{
return true;
}
@Override
public InputChannels getInputChannels()
{
return new InputChannels(3);
}
@Override
public Block project(ConnectorSession session, Page page, SelectedPositions selectedPositions)
{
Block block = page.getBlock(0);
BlockBuilder blockBuilder = BIGINT.createBlockBuilder(new BlockBuilderStatus(), selectedPositions.size());
if (selectedPositions.isList()) {
int offset = selectedPositions.getOffset();
int[] positions = selectedPositions.getPositions();
for (int index = offset; index < offset + selectedPositions.size(); index++) {
blockBuilder.writeLong(verifyPositive(block.getLong(positions[index], 0)));
}
}
else {
int offset = selectedPositions.getOffset();
for (int position = offset; position < offset + selectedPositions.size(); position++) {
blockBuilder.writeLong(verifyPositive(block.getLong(position, 0)));
}
}
return blockBuilder.build();
}
private static long verifyPositive(long value)
{
if (value < 0) {
throw new IllegalArgumentException("value is negative: " + value);
}
return value;
}
}
}