diff --git a/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java index 2fcc0fda3b52..eb26ff3241b5 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java @@ -20,43 +20,35 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import static io.airlift.slice.SizeOf.instanceSize; -import static java.util.Objects.requireNonNull; class TypedPositionsAppender implements PositionsAppender { private static final int INSTANCE_SIZE = instanceSize(TypedPositionsAppender.class); - private final Type type; private BlockBuilder blockBuilder; TypedPositionsAppender(Type type, int expectedPositions) { - this.type = requireNonNull(type, "type is null"); this.blockBuilder = type.createBlockBuilder(null, expectedPositions); } @Override - public void append(IntArrayList positions, ValueBlock source) + public void append(IntArrayList positions, ValueBlock block) { - int[] positionArray = positions.elements(); - for (int i = 0; i < positions.size(); i++) { - type.appendTo(source, positionArray[i], blockBuilder); - } + blockBuilder.appendPositions(block, positions.elements(), 0, positions.size()); } @Override - public void appendRle(ValueBlock block, int rlePositionCount) + public void appendRle(ValueBlock block, int count) { - for (int i = 0; i < rlePositionCount; i++) { - type.appendTo(block, 0, blockBuilder); - } + blockBuilder.appendRepeated(block, 0, count); } @Override - public void append(int position, ValueBlock source) + public void append(int position, ValueBlock block) { - type.appendTo(source, position, blockBuilder); + blockBuilder.append(block, position); } @Override diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 36a598fa6590..eb497d8c8ffc 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -220,6 +220,33 @@ java.method.removed method <E extends java.lang.Throwable> void io.trino.spi.block.VariableWidthBlockBuilder::buildEntry(io.trino.spi.block.VariableWidthBlockBuilder.VariableWidthEntryBuilder<E>) throws E + + true + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::append(io.trino.spi.block.ValueBlock, int) + + + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::appendPositions(io.trino.spi.block.ValueBlock, int[], int, int) + + + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::appendRange(io.trino.spi.block.ValueBlock, int, int) + + + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::appendRepeated(io.trino.spi.block.ValueBlock, int, int) + + + java.method.returnTypeChangedCovariantly + method io.trino.spi.block.BlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::appendNull() + method io.trino.spi.block.ShortArrayBlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::appendNull() + + + java.method.returnTypeChangedCovariantly + method io.trino.spi.block.BlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::writeShort(short) + method io.trino.spi.block.ShortArrayBlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::writeShort(short) + diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java index 9aad1d9da67e..ee7ab4f5bc85 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java @@ -177,6 +177,11 @@ int[] getOffsets() return offsets; } + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + int getOffsetBase() { return arrayOffset; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java index df28648003e7..4e5baa0c5bbd 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java @@ -21,6 +21,8 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; import static io.trino.spi.block.ArrayBlock.createArrayBlockInternal; +import static io.trino.spi.block.BlockUtil.appendRawBlockRange; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; import static java.util.Objects.requireNonNull; @@ -28,6 +30,7 @@ public class ArrayBlockBuilder implements BlockBuilder { private static final int INSTANCE_SIZE = instanceSize(ArrayBlockBuilder.class); + private static final int SIZE_IN_BYTES_PER_POSITION = Integer.BYTES + Byte.BYTES; private int positionCount; @@ -39,7 +42,7 @@ public class ArrayBlockBuilder private int[] offsets = new int[1]; private boolean[] valueIsNull = new boolean[0]; private boolean hasNullValue; - private boolean hasNonNullRow; + private boolean hasNonNullValue; private final BlockBuilder values; private boolean currentEntryOpened; @@ -82,7 +85,7 @@ private ArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, Block this.values = requireNonNull(values, "values is null"); this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } @Override @@ -94,7 +97,7 @@ public int getPositionCount() @Override public long getSizeInBytes() { - return values.getSizeInBytes() + ((Integer.BYTES + Byte.BYTES) * (long) positionCount); + return values.getSizeInBytes() + (SIZE_IN_BYTES_PER_POSITION * (long) positionCount); } @Override @@ -116,6 +119,97 @@ public void buildEntry(ArrayValueBuilder builder) currentEntryOpened = false; } + @Override + public void append(ValueBlock block, int position) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + ArrayBlock arrayBlock = (ArrayBlock) block; + if (block.isNull(position)) { + entryAdded(true); + return; + } + + int offsetBase = arrayBlock.getOffsetBase(); + int[] offsets = arrayBlock.getOffsets(); + int startOffset = offsets[offsetBase + position]; + int length = offsets[offsetBase + position + 1] - startOffset; + + appendRawBlockRange(arrayBlock.getRawElementBlock(), startOffset, length, values); + entryAdded(false); + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < count; i++) { + append(block, position); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + ensureCapacity(positionCount + length); + + ArrayBlock arrayBlock = (ArrayBlock) block; + + int rawOffsetBase = arrayBlock.getOffsetBase(); + int[] rawOffsets = arrayBlock.getOffsets(); + int startOffset = rawOffsets[rawOffsetBase + offset]; + int endOffset = rawOffsets[rawOffsetBase + offset + length]; + + appendRawBlockRange(arrayBlock.getRawElementBlock(), startOffset, endOffset - startOffset, values); + + // update offsets for copied data + for (int i = 0; i < length; i++) { + int entrySize = rawOffsets[rawOffsetBase + offset + i + 1] - rawOffsets[rawOffsetBase + offset + i]; + offsets[positionCount + i + 1] = offsets[positionCount + i] + entrySize; + } + + boolean[] rawValueIsNull = arrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffsetBase + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < length; i++) { + append(block, positions[offset + i]); + } + } + @Override public BlockBuilder appendNull() { @@ -129,37 +223,41 @@ public BlockBuilder appendNull() private void entryAdded(boolean isNull) { - if (valueIsNull.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); + offsets[positionCount + 1] = values.getPositionCount(); valueIsNull[positionCount] = isNull; hasNullValue |= isNull; - hasNonNullRow |= !isNull; + hasNonNullValue |= !isNull; positionCount++; if (blockBuilderStatus != null) { - blockBuilderStatus.addBytes(Integer.BYTES + Byte.BYTES); + blockBuilderStatus.addBytes(SIZE_IN_BYTES_PER_POSITION); } } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (valueIsNull.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); offsets = Arrays.copyOf(offsets, newSize + 1); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(offsets); if (blockBuilderStatus != null) { @@ -173,7 +271,7 @@ public Block build() if (currentEntryOpened) { throw new IllegalStateException("Current entry must be closed before the block can be built"); } - if (!hasNonNullRow) { + if (!hasNonNullValue) { return nullRle(positionCount); } return buildValueBlock(); @@ -197,10 +295,9 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus @Override public String toString() { - StringBuilder sb = new StringBuilder("ArrayBlockBuilder{"); - sb.append("positionCount=").append(getPositionCount()); - sb.append('}'); - return sb.toString(); + return "ArrayBlockBuilder{" + + "positionCount=" + getPositionCount() + + '}'; } private Block nullRle(int positionCount) diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java index 7d458991497e..97df9b75b235 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java @@ -36,6 +36,26 @@ public interface BlockBuilder */ long getRetainedSizeInBytes(); + /** + * Append the specified value. + */ + void append(ValueBlock block, int position); + + /** + * Append the specified value multiple times. + */ + void appendRepeated(ValueBlock block, int position, int count); + + /** + * Append the values in the specified range. + */ + void appendRange(ValueBlock block, int offset, int length); + + /** + * Append the values at the specified positions. + */ + void appendPositions(ValueBlock block, int[] positions, int offset, int length); + /** * Appends a null value to the block. */ diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java b/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java index 0f074c5f4310..06af44be190e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java @@ -357,4 +357,21 @@ else if (buffer.length < capacity) { return buffer; } + + static void appendRawBlockRange(Block rawBlock, int offset, int length, BlockBuilder blockBuilder) + { + rawBlock = rawBlock.getLoadedBlock(); + if (rawBlock instanceof RunLengthEncodedBlock rleBlock) { + blockBuilder.appendRepeated(rleBlock.getValue(), 0, length); + } + else if (rawBlock instanceof DictionaryBlock dictionaryBlock) { + blockBuilder.appendPositions(dictionaryBlock.getDictionary(), dictionaryBlock.getRawIds(), offset, length); + } + else if (rawBlock instanceof ValueBlock valueBlock) { + blockBuilder.appendRange(valueBlock, offset, length); + } + else { + throw new IllegalArgumentException("Unsupported block type " + rawBlock.getClass().getSimpleName()); + } + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java index 744c6753445c..e5fa09d77611 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java @@ -242,4 +242,19 @@ Slice getValuesSlice() { return Slices.wrappedBuffer(values, arrayOffset, positionCount); } + + int getRawValuesOffset() + { + return arrayOffset; + } + + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + + byte[] getRawValues() + { + return values; + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java index 559ead304ead..ef9971ca5253 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class ByteArrayBlockBuilder @@ -47,14 +48,12 @@ public ByteArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, in this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public BlockBuilder writeByte(byte value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,147 @@ public BlockBuilder writeByte(byte value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + if (byteArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = byteArrayBlock.getByte(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + + if (byteArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + byte value = byteArrayBlock.getByte(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + int rawOffset = byteArrayBlock.getRawValuesOffset(); + + byte[] rawValues = byteArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = byteArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - if (values.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; } + ensureCapacity(positionCount + length); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + int rawOffset = byteArrayBlock.getRawValuesOffset(); + byte[] rawValues = byteArrayBlock.getRawValues(); + boolean[] rawValueIsNull = byteArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +238,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new ByteArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java index f38e059ad2f6..a8694f321806 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java @@ -294,11 +294,17 @@ public static int decodeFixed12Second(int[] values, int position) return values[offset + 2]; } - int getPositionOffset() + int getRawOffset() { return positionOffset; } + @Nullable + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + int[] getRawValues() { return values; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java index f0d9e278510f..3a30f6fca05c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static io.trino.spi.block.Fixed12Block.FIXED12_BYTES; import static io.trino.spi.block.Fixed12Block.encodeFixed12; import static java.lang.Math.max; @@ -49,14 +50,12 @@ public Fixed12BlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, int this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public void writeFixed12(long first, int second) { - if (valueIsNull.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); encodeFixed12(first, second, values, positionCount); @@ -68,12 +67,174 @@ public void writeFixed12(long first, int second) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + Fixed12Block fixed12Block = (Fixed12Block) block; + if (fixed12Block.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + int[] rawValues = fixed12Block.getRawValues(); + int rawValuePosition = (fixed12Block.getRawOffset() + position) * 3; + + int positionIndex = positionCount * 3; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + values[positionIndex + 2] = rawValues[rawValuePosition + 2]; + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(Fixed12Block.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + Fixed12Block fixed12Block = (Fixed12Block) block; + if (fixed12Block.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + int[] rawValues = fixed12Block.getRawValues(); + int rawValuePosition = (fixed12Block.getRawOffset() + position) * 3; + int valueFirst = rawValues[rawValuePosition]; + int valueSecond = rawValues[rawValuePosition + 1]; + int valueThird = rawValues[rawValuePosition + 2]; + + int positionIndex = positionCount * 3; + for (int i = 0; i < count; i++) { + values[positionIndex] = valueFirst; + values[positionIndex + 1] = valueSecond; + values[positionIndex + 2] = valueThird; + positionIndex += 3; + } + + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * Fixed12Block.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + Fixed12Block fixed12Block = (Fixed12Block) block; + int rawOffset = fixed12Block.getRawOffset(); + + int[] rawValues = fixed12Block.getRawValues(); + System.arraycopy(rawValues, (rawOffset + offset) * 3, values, positionCount * 3, length * 3); + + boolean[] rawValueIsNull = fixed12Block.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - if (valueIsNull.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; } + ensureCapacity(positionCount + length); + + Fixed12Block fixed12Block = (Fixed12Block) block; + int rawOffset = fixed12Block.getRawOffset(); + int[] rawValues = fixed12Block.getRawValues(); + boolean[] rawValueIsNull = fixed12Block.getRawValueIsNull(); + + int positionIndex = positionCount * 3; + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + int rawValuePosition = rawPosition * 3; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + values[positionIndex + 2] = rawValues[rawValuePosition + 2]; + hasNonNullValue = true; + } + positionIndex += 3; + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + int rawValuePosition = rawPosition * 3; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + values[positionIndex + 2] = rawValues[rawValuePosition + 2]; + positionIndex += 3; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * Fixed12Block.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -105,23 +266,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new Fixed12BlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (valueIsNull.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize * 3); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java index 131837f74c86..5d1799f83c4a 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java @@ -40,7 +40,7 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO encodeNullsAsBits(sliceOutput, fixed12Block); if (!fixed12Block.mayHaveNull()) { - sliceOutput.writeInts(fixed12Block.getRawValues(), fixed12Block.getPositionOffset() * 3, fixed12Block.getPositionCount() * 3); + sliceOutput.writeInts(fixed12Block.getRawValues(), fixed12Block.getRawOffset() * 3, fixed12Block.getPositionCount() * 3); } else { int[] valuesWithoutNull = new int[positionCount * 3]; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java index 57b8e4ac9bd4..0cdf686a3d21 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java @@ -256,13 +256,19 @@ public String toString() return sb.toString(); } - long[] getRawValues() + int getRawOffset() { - return values; + return positionOffset; } - int getPositionOffset() + @Nullable + boolean[] getRawValueIsNull() { - return positionOffset; + return valueIsNull; + } + + long[] getRawValues() + { + return values; } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java index f22ae8951fea..5d512b718453 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class Int128ArrayBlockBuilder @@ -47,14 +48,12 @@ public Int128ArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public void writeInt128(long high, long low) { - if (valueIsNull.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); int valueIndex = positionCount * 2; values[valueIndex] = high; @@ -68,12 +67,168 @@ public void writeInt128(long high, long low) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + if (int128ArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + long[] rawValues = int128ArrayBlock.getRawValues(); + int rawValuePosition = (int128ArrayBlock.getRawOffset() + position) * 2; + + int positionIndex = positionCount * 2; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + if (int128ArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + long[] rawValues = int128ArrayBlock.getRawValues(); + int rawValuePosition = (int128ArrayBlock.getRawOffset() + position) * 2; + long valueHigh = rawValues[rawValuePosition]; + long valueLow = rawValues[rawValuePosition + 1]; + + int positionIndex = positionCount * 2; + for (int i = 0; i < count; i++) { + values[positionIndex] = valueHigh; + values[positionIndex + 1] = valueLow; + positionIndex += 2; + } + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + int rawOffset = int128ArrayBlock.getRawOffset(); + + long[] rawValues = int128ArrayBlock.getRawValues(); + System.arraycopy(rawValues, (rawOffset + offset) * 2, values, positionCount * 2, length * 2); + + boolean[] rawValueIsNull = int128ArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - if (valueIsNull.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; } + ensureCapacity(positionCount + length); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + int rawOffset = int128ArrayBlock.getRawOffset(); + long[] rawValues = int128ArrayBlock.getRawValues(); + boolean[] rawValueIsNull = int128ArrayBlock.getRawValueIsNull(); + + int positionIndex = positionCount * 2; + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + int rawValuePosition = rawPosition * 2; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + hasNonNullValue = true; + } + positionIndex += 2; + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + int rawValuePosition = rawPosition * 2; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + positionIndex += 2; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -105,23 +260,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new Int128ArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (valueIsNull.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize * 2); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java index 78e8191202e5..55539440d8cd 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java @@ -40,7 +40,7 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO encodeNullsAsBits(sliceOutput, int128ArrayBlock); if (!int128ArrayBlock.mayHaveNull()) { - sliceOutput.writeLongs(int128ArrayBlock.getRawValues(), int128ArrayBlock.getPositionOffset() * 2, int128ArrayBlock.getPositionCount() * 2); + sliceOutput.writeLongs(int128ArrayBlock.getRawValues(), int128ArrayBlock.getRawOffset() * 2, int128ArrayBlock.getPositionCount() * 2); } else { long[] valuesWithoutNull = new long[positionCount * 2]; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java index 93fa86da8456..11d426889fb2 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java @@ -237,6 +237,11 @@ public String toString() return sb.toString(); } + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + @Experimental(eta = "2023-12-31") public int[] getRawValues() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java index bf124103418b..50ebd1029e23 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class IntArrayBlockBuilder @@ -47,14 +48,12 @@ public IntArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, int this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public BlockBuilder writeInt(int value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,146 @@ public BlockBuilder writeInt(int value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + if (intArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = intArrayBlock.getInt(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) { - if (values.length <= positionCount) { - growCapacity(); + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; } + ensureCapacity(positionCount + count); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + if (intArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + int value = intArrayBlock.getInt(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + int rawOffset = intArrayBlock.getRawValuesOffset(); + + int[] rawValues = intArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = intArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + int rawOffset = intArrayBlock.getRawValuesOffset(); + int[] rawValues = intArrayBlock.getRawValues(); + boolean[] rawValueIsNull = intArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +237,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new IntArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java index 2b9aec633844..b79e5939ba79 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java @@ -283,13 +283,18 @@ public String toString() return sb.toString(); } + int getRawValuesOffset() + { + return arrayOffset; + } + long[] getRawValues() { return values; } - int getRawValuesOffset() + boolean[] getRawValueIsNull() { - return arrayOffset; + return valueIsNull; } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java index 09a530971ac1..059709edbb1e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class LongArrayBlockBuilder @@ -47,14 +48,12 @@ public LongArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, in this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public BlockBuilder writeLong(long value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,146 @@ public BlockBuilder writeLong(long value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + if (longArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = longArrayBlock.getLong(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) { - if (values.length <= positionCount) { - growCapacity(); + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; } + ensureCapacity(positionCount + count); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + if (longArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + long value = longArrayBlock.getLong(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + int rawOffset = longArrayBlock.getRawValuesOffset(); + + long[] rawValues = longArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = longArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + int rawOffset = longArrayBlock.getRawValuesOffset(); + long[] rawValues = longArrayBlock.getRawValues(); + boolean[] rawValueIsNull = longArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +237,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new LongArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java index 477ae48f4ce1..c1e4af377f2c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java @@ -23,6 +23,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.appendRawBlockRange; import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static io.trino.spi.block.MapBlock.createMapBlockInternal; import static io.trino.spi.block.MapHashTables.HASH_MULTIPLIER; @@ -132,6 +133,64 @@ public void buildEntry(MapValueBuilder builder) currentEntryOpened = false; } + @Override + public void append(ValueBlock block, int position) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + MapBlock mapBlock = (MapBlock) block; + if (block.isNull(position)) { + entryAdded(true); + return; + } + + int offsetBase = mapBlock.getOffsetBase(); + int[] offsets = mapBlock.getOffsets(); + int startOffset = offsets[offsetBase + position]; + int length = offsets[offsetBase + position + 1] - startOffset; + + appendRawBlockRange(mapBlock.getRawKeyBlock(), startOffset, length, keyBlockBuilder); + appendRawBlockRange(mapBlock.getRawValueBlock(), startOffset, length, valueBlockBuilder); + entryAdded(false); + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + // this could be optimized to append all array elements using a single append range call + for (int i = 0; i < length; i++) { + append(block, offset + i); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < count; i++) { + append(block, position); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < length; i++) { + append(block, positions[offset + i]); + } + } + @Override public BlockBuilder appendNull() { @@ -167,6 +226,29 @@ private void entryAdded(boolean isNull) @Override public Block build() { + if (positionCount > 1 && hasNullValue) { + boolean hasNonNull = false; + for (int i = 0; i < positionCount; i++) { + hasNonNull |= !mapIsNull[i]; + } + if (!hasNonNull) { + Block emptyKeyBlock = mapType.getKeyType().createBlockBuilder(null, 0).build(); + Block emptyValueBlock = mapType.getValueType().createBlockBuilder(null, 0).build(); + int[] emptyOffsets = {0, 0}; + boolean[] nulls = {true}; + return RunLengthEncodedBlock.create( + createMapBlockInternal( + mapType, + 0, + 1, + Optional.of(nulls), + emptyOffsets, + emptyKeyBlock, + emptyValueBlock, + MapHashTables.create(hashBuildMode, mapType, 0, emptyKeyBlock, emptyOffsets, nulls)), + positionCount); + } + } return buildValueBlock(); } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java index 930f82bbe50a..2e4d98ee2769 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java @@ -152,6 +152,11 @@ public boolean mayHaveNull() return rowIsNull != null; } + boolean[] getRawRowIsNull() + { + return rowIsNull; + } + @Override public int getPositionCount() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java index 291c97c1b9ae..ea1a16f4bdf9 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java @@ -112,6 +112,205 @@ public void buildEntry(RowValueBuilder builder) currentEntryOpened = false; } + @Override + public void append(ValueBlock block, int position) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + RowBlock rowBlock = (RowBlock) block; + if (block.isNull(position)) { + appendNull(); + return; + } + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendToField(fieldBlocks.get(fieldId), position, fieldBlockBuilders[fieldId]); + } + entryAdded(false); + } + + private static void appendToField(Block fieldBlock, int position, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.append(rleBlock.getValue(), 0); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + fieldBlockBuilder.append(dictionaryBlock.getDictionary(), dictionaryBlock.getId(position)); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.append(valueBlock, position); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + if (length == 0) { + return; + } + + RowBlock rowBlock = (RowBlock) block; + ensureCapacity(positionCount + length); + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendRangeToField(fieldBlocks.get(fieldId), offset, length, fieldBlockBuilders[fieldId]); + } + + boolean[] rawRowIsNull = rowBlock.getRawRowIsNull(); + if (rawRowIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawRowIsNull[offset + i]) { + rowIsNull[positionCount + i] = true; + hasNullRow = true; + } + else { + hasNonNullRow = true; + } + } + } + else { + hasNonNullRow = true; + } + positionCount += length; + } + + private static void appendRangeToField(Block fieldBlock, int offset, int length, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.appendRepeated(rleBlock.getValue(), 0, length); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + int[] rawIds = dictionaryBlock.getRawIds(); + int rawIdsOffset = dictionaryBlock.getRawIdsOffset(); + fieldBlockBuilder.appendPositions(dictionaryBlock.getDictionary(), rawIds, rawIdsOffset + offset, length); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.appendRange(valueBlock, offset, length); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + if (count == 0) { + return; + } + + RowBlock rowBlock = (RowBlock) block; + ensureCapacity(positionCount + count); + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendRepeatedToField(fieldBlocks.get(fieldId), position, count, fieldBlockBuilders[fieldId]); + } + + if (rowBlock.isNull(position)) { + Arrays.fill(rowIsNull, positionCount, positionCount + count, true); + hasNullRow = true; + } + else { + hasNonNullRow = true; + } + + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(Integer.BYTES + Byte.BYTES); + } + } + + private static void appendRepeatedToField(Block fieldBlock, int position, int count, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.appendRepeated(rleBlock.getValue(), 0, count); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + fieldBlockBuilder.appendRepeated(dictionaryBlock.getDictionary(), dictionaryBlock.getId(position), count); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.appendRepeated(valueBlock, position, count); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + if (length == 0) { + return; + } + + RowBlock rowBlock = (RowBlock) block; + ensureCapacity(positionCount + length); + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendPositionsToField(fieldBlocks.get(fieldId), positions, offset, length, fieldBlockBuilders[fieldId]); + } + + boolean[] rawRowIsNull = rowBlock.getRawRowIsNull(); + if (rawRowIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawRowIsNull[positions[offset + i]]) { + rowIsNull[positionCount + i] = true; + hasNullRow = true; + } + else { + hasNonNullRow = true; + } + } + } + else { + hasNonNullRow = true; + } + positionCount += length; + } + + private static void appendPositionsToField(Block fieldBlock, int[] positions, int offset, int length, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.appendRepeated(rleBlock.getValue(), 0, length); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + int[] newPositions = new int[length]; + for (int i = 0; i < newPositions.length; i++) { + newPositions[i] = dictionaryBlock.getId(positions[offset + i]); + } + fieldBlockBuilder.appendPositions(dictionaryBlock.getDictionary(), newPositions, 0, length); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.appendPositions(valueBlock, positions, offset, length); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + @Override public BlockBuilder appendNull() { @@ -129,10 +328,7 @@ public BlockBuilder appendNull() private void entryAdded(boolean isNull) { - if (rowIsNull.length <= positionCount) { - int newSize = BlockUtil.calculateNewArraySize(rowIsNull.length); - rowIsNull = Arrays.copyOf(rowIsNull, newSize); - } + ensureCapacity(positionCount + 1); rowIsNull[positionCount] = isNull; hasNullRow |= isNull; @@ -176,6 +372,17 @@ public RowBlock buildValueBlock() return createRowBlockInternal(positionCount, hasNullRow ? rowIsNull : null, fieldBlocks); } + private void ensureCapacity(int capacity) + { + if (rowIsNull.length >= capacity) { + return; + } + + // todo add lazy initialize + int newSize = BlockUtil.calculateNewArraySize(rowIsNull.length); + rowIsNull = Arrays.copyOf(rowIsNull, newSize); + } + @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java index 336a5b845539..88ed0b470405 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java @@ -244,4 +244,9 @@ short[] getRawValues() { return values; } + + boolean[] getRawValueIsNull() + { + return valueIsNull; + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java index ee44b44b6dc2..a18d02142b1a 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class ShortArrayBlockBuilder @@ -47,14 +48,12 @@ public ShortArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, i this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } - public BlockBuilder writeShort(short value) + public ShortArrayBlockBuilder writeShort(short value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,146 @@ public BlockBuilder writeShort(short value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + if (shortArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = shortArrayBlock.getShort(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + if (shortArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + short value = shortArrayBlock.getShort(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) { - if (values.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; } + ensureCapacity(positionCount + length); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + int rawOffset = shortArrayBlock.getRawValuesOffset(); + + short[] rawValues = shortArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = shortArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + int rawOffset = shortArrayBlock.getRawValuesOffset(); + short[] rawValues = shortArrayBlock.getRawValues(); + boolean[] rawValueIsNull = shortArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public ShortArrayBlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +237,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new ShortArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java index 931d4cae70eb..58bc8535bebb 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java @@ -332,6 +332,21 @@ public VariableWidthBlock copyWithAppendedNull() return new VariableWidthBlock(arrayOffset, positionCount + 1, slice, newOffsets, newValueIsNull); } + int getRawArrayBase() + { + return arrayOffset; + } + + int[] getRawOffsets() + { + return offsets; + } + + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + @Override public VariableWidthBlock getUnderlyingValueBlock() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java index ba53ef75c57c..59aca4f3b550 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java @@ -19,16 +19,14 @@ import java.util.Arrays; -import static io.airlift.slice.SizeOf.SIZE_OF_BYTE; -import static io.airlift.slice.SizeOf.SIZE_OF_INT; import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; import static io.airlift.slice.Slices.EMPTY_SLICE; import static io.trino.spi.block.BlockUtil.MAX_ARRAY_SIZE; import static io.trino.spi.block.BlockUtil.calculateBlockResetBytes; import static io.trino.spi.block.BlockUtil.calculateNewArraySize; -import static java.lang.Math.max; import static java.lang.Math.min; +import static java.lang.Math.toIntExact; public class VariableWidthBlockBuilder implements BlockBuilder @@ -79,7 +77,7 @@ public long getSizeInBytes() @Override public long getRetainedSizeInBytes() { - long size = INSTANCE_SIZE + sizeOf(bytes) + arraysRetainedSizeInBytes; + long size = INSTANCE_SIZE + arraysRetainedSizeInBytes; if (blockBuilderStatus != null) { size += BlockBuilderStatus.INSTANCE_SIZE; } @@ -108,53 +106,247 @@ public VariableWidthBlockBuilder writeEntry(byte[] source, int sourceIndex, int } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) { - hasNullValue = true; - entryAdded(0, true); - return this; + ensureCapacity(positionCount + 1); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int bytesWritten = 0; + if (variableWidthBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + int startValueOffset = rawOffsets[rawArrayBase + position]; + int endValueOffset = rawOffsets[rawArrayBase + position + 1]; + int length = endValueOffset - startValueOffset; + ensureFreeSpace(length); + + Slice rawSlice = variableWidthBlock.getRawSlice(); + byte[] rawByteArray = rawSlice.byteArray(); + int byteArrayOffset = rawSlice.byteArrayOffset(); + + System.arraycopy(rawByteArray, byteArrayOffset + startValueOffset, bytes, offsets[positionCount], length); + bytesWritten = length; + hasNonNullValue = true; + } + offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(SIZE_IN_BYTES_PER_POSITION + bytesWritten); + } } - private void entryAdded(int bytesWritten, boolean isNull) + @Override + public void appendRepeated(ValueBlock block, int position, int count) { - if (valueIsNull.length <= positionCount) { - growCapacity(); + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; } - valueIsNull[positionCount] = isNull; - offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; + ensureCapacity(positionCount + count); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int bytesWritten = 0; + if (variableWidthBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + Arrays.fill(offsets, positionCount + 1, positionCount + count + 1, offsets[positionCount]); + hasNullValue = true; + } + else { + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + int startValueOffset = rawOffsets[rawArrayBase + position]; + int endValueOffset = rawOffsets[rawArrayBase + position + 1]; + int length = endValueOffset - startValueOffset; + + if (length > 0) { + bytesWritten = toIntExact((long) length * count); + ensureFreeSpace(bytesWritten); + + // copy in the value + Slice rawSlice = variableWidthBlock.getRawSlice(); + byte[] rawByteArray = rawSlice.byteArray(); + int byteArrayOffset = rawSlice.byteArrayOffset(); + + int currentOffset = offsets[positionCount]; + System.arraycopy(rawByteArray, byteArrayOffset + startValueOffset, bytes, currentOffset, length); + + // repeatedly duplicate the written vales, doubling the number of values copied each time + int duplicatedBytes = length; + while (duplicatedBytes * 2 <= bytesWritten) { + System.arraycopy(bytes, currentOffset, bytes, currentOffset + duplicatedBytes, duplicatedBytes); + duplicatedBytes = duplicatedBytes * 2; + } + // copy the remaining values + System.arraycopy(bytes, currentOffset, bytes, currentOffset + duplicatedBytes, bytesWritten - duplicatedBytes); + + // set the offsets + int previousOffset = currentOffset; + for (int i = 0; i < count; i++) { + previousOffset += length; + offsets[positionCount + i + 1] = previousOffset; + } + } + else { + // zero length array + Arrays.fill(offsets, positionCount + 1, positionCount + count + 1, offsets[positionCount]); + } + + hasNonNullValue = true; + } + positionCount += count; - positionCount++; - hasNonNullValue |= !isNull; if (blockBuilderStatus != null) { - blockBuilderStatus.addBytes(SIZE_OF_BYTE + SIZE_OF_INT + bytesWritten); + blockBuilderStatus.addBytes(count * SIZE_IN_BYTES_PER_POSITION + bytesWritten); } } - private void growCapacity() + @Override + public void appendRange(ValueBlock block, int offset, int length) { - int newSize = calculateNewArraySize(valueIsNull.length, initialEntryCount); - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - offsets = Arrays.copyOf(offsets, newSize + 1); - updateRetainedSize(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + int startValueOffset = rawOffsets[rawArrayBase + offset]; + int totalSize = rawOffsets[rawArrayBase + offset + length] - startValueOffset; + // grow the buffer for the new data + ensureFreeSpace(totalSize); + + Slice sourceSlice = variableWidthBlock.getRawSlice(); + System.arraycopy(sourceSlice.byteArray(), sourceSlice.byteArrayOffset() + startValueOffset, bytes, offsets[positionCount], totalSize); + + // update offsets for copied data + int offsetDelta = offsets[positionCount] - rawOffsets[rawArrayBase + offset]; + for (int i = 0; i < length; i++) { + offsets[positionCount + i + 1] = rawOffsets[rawArrayBase + offset + i + 1] + offsetDelta; + } + + // update nulls + boolean[] rawValueIsNull = variableWidthBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawArrayBase + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * SIZE_IN_BYTES_PER_POSITION); + } } - private void ensureFreeSpace(int extraBytesCapacity) + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - int requiredSize = offsets[positionCount] + extraBytesCapacity; - if (bytes.length < requiredSize) { - int newBytesLength = max(bytes.length, initialSliceOutputSize); - if (requiredSize > newBytesLength) { - newBytesLength = max(requiredSize, calculateNewArraySize(newBytesLength)); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + + // update the offsets and compute the total size + int initialOffset = offsets[positionCount]; + int totalSize = 0; + for (int i = 0; i < length; i++) { + int position = positions[offset + i]; + totalSize += rawOffsets[rawArrayBase + position + 1] - rawOffsets[rawArrayBase + position]; + offsets[positionCount + i + 1] = initialOffset + totalSize; + } + // grow the buffer for the new data + ensureFreeSpace(totalSize); + + // copy values to buffer + Slice rawSlice = variableWidthBlock.getRawSlice(); + byte[] sourceBytes = rawSlice.byteArray(); + int sourceBytesOffset = rawSlice.byteArrayOffset(); + for (int i = 0; i < length; i++) { + int position = positions[offset + i]; + int sourceStart = rawOffsets[rawArrayBase + position]; + int sourceLength = rawOffsets[rawArrayBase + position + 1] - sourceStart; + System.arraycopy(sourceBytes, sourceBytesOffset + sourceStart, bytes, offsets[positionCount + i], sourceLength); + totalSize += sourceLength; + } + + // update nulls + boolean[] rawValueIsNull = variableWidthBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawArrayBase; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } } - bytes = Arrays.copyOf(bytes, newBytesLength); - updateRetainedSize(); + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * SIZE_IN_BYTES_PER_POSITION + totalSize); } } - private void updateRetainedSize() + @Override + public BlockBuilder appendNull() { - arraysRetainedSizeInBytes = sizeOf(valueIsNull) + sizeOf(offsets); + hasNullValue = true; + entryAdded(0, true); + return this; + } + + private void entryAdded(int bytesWritten, boolean isNull) + { + ensureCapacity(positionCount + 1); + + valueIsNull[positionCount] = isNull; + offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; + + positionCount++; + hasNonNullValue |= !isNull; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(SIZE_IN_BYTES_PER_POSITION + bytesWritten); + } } @Override @@ -184,6 +376,35 @@ private int getOffset(int position) return offsets[position]; } + private void ensureCapacity(int capacity) + { + if (valueIsNull.length >= capacity) { + return; + } + + int newSize = calculateNewArraySize(capacity, initialEntryCount); + valueIsNull = Arrays.copyOf(valueIsNull, newSize); + offsets = Arrays.copyOf(offsets, newSize + 1); + updateRetainedSize(); + } + + private void ensureFreeSpace(int extraBytesCapacity) + { + int requiredSize = offsets[positionCount] + extraBytesCapacity; + if (bytes.length >= requiredSize) { + return; + } + + int newSize = calculateNewArraySize(requiredSize, initialSliceOutputSize); + bytes = Arrays.copyOf(bytes, newSize); + updateRetainedSize(); + } + + private void updateRetainedSize() + { + arraysRetainedSizeInBytes = sizeOf(valueIsNull) + sizeOf(offsets) + sizeOf(bytes); + } + @Override public String toString() { diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java new file mode 100644 index 000000000000..bcc0057aded7 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java @@ -0,0 +1,236 @@ +/* + * 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 io.trino.spi.block; + +import com.google.common.collect.Iterables; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.nCopies; +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class AbstractTestBlockBuilder +{ + protected abstract BlockBuilder createBlockBuilder(); + + protected abstract List getTestValues(); + + protected abstract T getUnusedTestValue(); + + protected abstract ValueBlock blockFromValues(Iterable values); + + protected abstract List blockToValues(ValueBlock valueBlock); + + @Test + public void verifyTestData() + { + List values = getTestValues(); + assertThat(values) + .hasSize(5) + .doesNotHaveDuplicates() + .doesNotContainNull() + .doesNotContain(getUnusedTestValue()); + + ValueBlock valueBlock = blockFromValues(values); + assertThat(blockToValues(valueBlock)).isEqualTo(values); + } + + @Test + public void testAppend() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.append(inputValues, 1); + blockBuilder.append(inputValues, 3); + blockBuilder.append(inputValues, 1); + blockBuilder.append(inputValues, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(values.get(1), values.get(3), values.get(1), values.get(3)); + } + + @Test + public void testAppendWithNulls() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.append(inputValues, 1); + blockBuilder.append(inputValues, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).hasSize(2).containsOnlyNulls(); + + // add a non-null value + blockBuilder.append(inputValues, 2); + valueBlock = blockBuilder.buildValueBlock(); + + actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(null, null, values.get(2)); + } + + @Test + public void testAppendRepeated() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRepeated(inputValues, 1, 10); + blockBuilder.appendRepeated(inputValues, 3, 10); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactlyElementsOf(Iterables.concat(nCopies(10, values.get(1)), nCopies(10, values.get(3)))); + } + + @Test + public void testAppendRepeatedWithNulls() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRepeated(inputValues, 1, 10); + blockBuilder.appendRepeated(inputValues, 3, 10); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).hasSize(20).containsOnlyNulls(); + + // an all-null block should be converted to a RunLengthEncodedBlock + assertThat(blockBuilder.build()).isInstanceOf(RunLengthEncodedBlock.class); + + // add some non-null values + blockBuilder.appendRepeated(inputValues, 2, 10); + valueBlock = blockBuilder.buildValueBlock(); + + actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactlyElementsOf(Iterables.concat(nCopies(20, null), nCopies(10, values.get(2)))); + } + + @Test + public void testAppendRange() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRange(inputValues, 1, 3); + blockBuilder.appendRange(inputValues, 2, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactlyElementsOf(Iterables.concat(values.subList(1, 4), values.subList(2, 5))); + } + + @Test + public void testAppendRangeWithNulls() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRange(inputValues, 1, 3); + blockBuilder.appendRange(inputValues, 2, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(null, values.get(2), null, values.get(2), null, values.get(4)); + } + + @Test + public void testAppendPositions() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendPositions(inputValues, new int[] {-100, 1, 3, 2, -100}, 1, 3); + blockBuilder.appendPositions(inputValues, new int[] {-100, 4, 0, -100}, 1, 2); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(values.get(1), values.get(3), values.get(2), values.get(4), values.get(0)); + } + + @Test + public void testAppendPositionsWithNull() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendPositions(inputValues, new int[] {-100, 1, 3, 2, -100}, 1, 3); + blockBuilder.appendPositions(inputValues, new int[] {-100, 4, 0, -100}, 1, 2); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(null, null, values.get(2), values.get(4), values.get(0)); + } + + /** + * Create a block that is offset from the start of the underlying array + */ + private ValueBlock createOffsetBlock(List values) + { + return blockFromValues(Iterables.concat(nCopies(2, getUnusedTestValue()), values, nCopies(2, getUnusedTestValue()))) + .getRegion(2, values.size()); + } + + /** + * Create a block that is offset from the start of the underlying array + */ + private ValueBlock createOffsetBlockWithOddPositionsNull(List values) + { + ArrayList blockValues = new ArrayList<>(); + blockValues.add(getUnusedTestValue()); + blockValues.add(getUnusedTestValue()); + for (int i = 0; i < values.size(); i++) { + T value = values.get(i); + if (i % 2 == 0) { + blockValues.add(value); + } + else { + blockValues.add(null); + } + } + blockValues.add(getUnusedTestValue()); + blockValues.add(getUnusedTestValue()); + return blockFromValues(blockValues).getRegion(2, values.size()); + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java index 511961cb8260..689504f8f927 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java @@ -15,13 +15,19 @@ import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import static io.airlift.slice.SizeOf.instanceSize; import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Long.BYTES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestArrayBlockBuilder + extends AbstractTestBlockBuilder> { // ArrayBlockBuilder: isNull, offset, 3 * value (FixedWidthBlockBuilder: isNull, value) private static final int THREE_INTS_ENTRY_SIZE = Byte.BYTES + Integer.BYTES + 3 * (Byte.BYTES + Long.BYTES); @@ -107,4 +113,77 @@ private static void assertIsAllNulls(Block block, int expectedPositionCount) assertThat(block.isNull(0)).isTrue(); } } + + @Override + protected BlockBuilder createBlockBuilder() + { + return new ArrayBlockBuilder(new VariableWidthBlockBuilder(null, 1, 100), null, 1); + } + + @Override + protected List> getTestValues() + { + return List.of( + List.of("a", "apple", "ape"), + Arrays.asList("b", null, "bear", "break"), + List.of("c", "cherry"), + Arrays.asList("d", "date", "dinosaur", null, "dirt"), + List.of("e", "eggplant", "empty", "")); + } + + @Override + protected List getUnusedTestValue() + { + return List.of("unused", "ignore me"); + } + + @Override + protected ValueBlock blockFromValues(Iterable> values) + { + ArrayBlockBuilder blockBuilder = new ArrayBlockBuilder(new VariableWidthBlockBuilder(null, 1, 100), null, 1); + for (List array : values) { + if (array == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.buildEntry(elementBuilder -> { + for (String entry : array) { + if (entry == null) { + elementBuilder.appendNull(); + } + else { + VARCHAR.writeString(elementBuilder, entry); + } + } + }); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List> blockToValues(ValueBlock valueBlock) + { + ArrayBlock block = (ArrayBlock) valueBlock; + List> actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + Block array = block.getArray(i); + ArrayList arrayBuilder = new ArrayList<>(); + for (int j = 0; j < array.getPositionCount(); j++) { + if (array.isNull(j)) { + arrayBuilder.add(null); + } + else { + arrayBuilder.add(VARCHAR.getSlice(array, j).toStringUtf8()); + } + } + actualValues.add(arrayBuilder); + } + } + return actualValues; + } } diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java new file mode 100644 index 000000000000..91463d2a473f --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestByteArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new ByteArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of((byte) 10, (byte) 11, (byte) 12, (byte) 13, (byte) 14); + } + + @Override + protected Byte getUnusedTestValue() + { + return (byte) -1; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + ByteArrayBlockBuilder blockBuilder = new ByteArrayBlockBuilder(null, 1); + for (Byte value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeByte(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) valueBlock; + List actualValues = new ArrayList<>(byteArrayBlock.getPositionCount()); + for (int i = 0; i < byteArrayBlock.getPositionCount(); i++) { + if (byteArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(byteArrayBlock.getByte(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java new file mode 100644 index 000000000000..392125972bfa --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java @@ -0,0 +1,72 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestFixed12BlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new Fixed12BlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(new Fixed12(90, 10), new Fixed12(91, 11), new Fixed12(92, 12), new Fixed12(93, 13), new Fixed12(94, 14)); + } + + @Override + protected Fixed12 getUnusedTestValue() + { + return new Fixed12(-1, -2); + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + Fixed12BlockBuilder blockBuilder = new Fixed12BlockBuilder(null, 1); + for (Fixed12 value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeFixed12(value.first(), value.second()); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + Fixed12Block fixed12ArrayBlock = (Fixed12Block) valueBlock; + List actualValues = new ArrayList<>(fixed12ArrayBlock.getPositionCount()); + for (int i = 0; i < fixed12ArrayBlock.getPositionCount(); i++) { + if (fixed12ArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(new Fixed12(fixed12ArrayBlock.getFixed12First(i), fixed12ArrayBlock.getFixed12Second(i))); + } + } + return actualValues; + } + + public record Fixed12(long first, int second) {} +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java new file mode 100644 index 000000000000..e25fe5af37f8 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java @@ -0,0 +1,72 @@ +/* + * 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 io.trino.spi.block; + +import io.trino.spi.type.Int128; + +import java.util.ArrayList; +import java.util.List; + +public class TestInt128ArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new Int128ArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(Int128.valueOf(90, 10), Int128.valueOf(91, 11), Int128.valueOf(92, 12), Int128.valueOf(93, 13), Int128.valueOf(94, 14)); + } + + @Override + protected Int128 getUnusedTestValue() + { + return Int128.valueOf(-1, -2); + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + Int128ArrayBlockBuilder blockBuilder = new Int128ArrayBlockBuilder(null, 1); + for (Int128 value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeInt128(value.getHigh(), value.getLow()); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) valueBlock; + List actualValues = new ArrayList<>(int128ArrayBlock.getPositionCount()); + for (int i = 0; i < int128ArrayBlock.getPositionCount(); i++) { + if (int128ArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(int128ArrayBlock.getInt128(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java new file mode 100644 index 000000000000..9996772166fd --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestIntArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new IntArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(10, 11, 12, 13, 14); + } + + @Override + protected Integer getUnusedTestValue() + { + return -1; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + IntArrayBlockBuilder blockBuilder = new IntArrayBlockBuilder(null, 1); + for (Integer value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeInt(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + IntArrayBlock intArrayBlock = (IntArrayBlock) valueBlock; + List actualValues = new ArrayList<>(intArrayBlock.getPositionCount()); + for (int i = 0; i < intArrayBlock.getPositionCount(); i++) { + if (intArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(intArrayBlock.getInt(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java new file mode 100644 index 000000000000..88766a0f3e67 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestLongArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new LongArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(10L, 11L, 12L, 13L, 14L); + } + + @Override + protected Long getUnusedTestValue() + { + return -1L; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + LongArrayBlockBuilder blockBuilder = new LongArrayBlockBuilder(null, 1); + for (Long value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeLong(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + LongArrayBlock longArrayBlock = (LongArrayBlock) valueBlock; + List actualValues = new ArrayList<>(longArrayBlock.getPositionCount()); + for (int i = 0; i < longArrayBlock.getPositionCount(); i++) { + if (longArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(longArrayBlock.getLong(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java new file mode 100644 index 000000000000..137d0cc6c2cb --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java @@ -0,0 +1,101 @@ +/* + * 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 io.trino.spi.block; + +import io.trino.spi.type.MapType; +import io.trino.spi.type.TypeOperators; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.VarcharType.VARCHAR; + +public class TestMapBlockBuilder + extends AbstractTestBlockBuilder> +{ + private static final TypeOperators TYPE_OPERATORS = new TypeOperators(); + private static final MapType MAP_TYPE = new MapType(VARCHAR, INTEGER, TYPE_OPERATORS); + + @Override + protected BlockBuilder createBlockBuilder() + { + return new MapBlockBuilder(MAP_TYPE, null, 1); + } + + @Override + protected List> getTestValues() + { + return List.of( + Map.of("a", 0, "apple", 1, "ape", 2), + Map.of("b", 3, "banana", 4, "bear", 5, "break", 6), + Map.of("c", 7, "cherry", 8), + Map.of("d", 9, "date", 10, "dinosaur", 11, "dinner", 12, "dirt", 13), + Map.of("e", 14, "eggplant", 15, "empty", 16, "", 17)); + } + + @Override + protected Map getUnusedTestValue() + { + return Map.of("unused", -1, "ignore me", -2); + } + + @Override + protected ValueBlock blockFromValues(Iterable> maps) + { + MapBlockBuilder blockBuilder = new MapBlockBuilder(MAP_TYPE, null, 1); + for (Map map : maps) { + if (map == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.buildEntry((keyBuilder, valueBuilder) -> { + for (Map.Entry entry : map.entrySet()) { + VARCHAR.writeString(keyBuilder, entry.getKey()); + INTEGER.writeLong(valueBuilder, entry.getValue()); + } + }); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List> blockToValues(ValueBlock valueBlock) + { + MapBlock block = (MapBlock) valueBlock; + List> actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + SqlMap sqlMap = block.getMap(i); + int rawOffset = sqlMap.getRawOffset(); + Block rawKeyBlock = sqlMap.getRawKeyBlock(); + Block rawValueBlock = sqlMap.getRawValueBlock(); + Map actualMap = new HashMap<>(); + for (int entryIndex = 0; entryIndex < sqlMap.getSize(); entryIndex++) { + actualMap.put( + VARCHAR.getSlice(rawKeyBlock, rawOffset + entryIndex).toStringUtf8(), + INTEGER.getInt(rawValueBlock, rawOffset + entryIndex)); + } + actualValues.add(actualMap); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java index c46a57ded70e..6befb3606c99 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java @@ -16,10 +16,17 @@ import com.google.common.collect.ImmutableList; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; public class TestRowBlockBuilder + extends AbstractTestBlockBuilder { @Test public void testBuilderProducesNullRleForNullRows() @@ -53,4 +60,73 @@ private static void assertIsAllNulls(Block block, int expectedPositionCount) assertThat(block.isNull(0)).isTrue(); } } + + @Override + protected BlockBuilder createBlockBuilder() + { + return new RowBlockBuilder(List.of(VARCHAR, INTEGER, BOOLEAN), null, 1); + } + + @Override + protected List getTestValues() + { + return List.of( + new TestRow("apple", 2, true), + new TestRow("bear", 5, false), + new TestRow(null, 7, true), + new TestRow("dinosaur", 9, false), + new TestRow("", 22, true)); + } + + @Override + protected TestRow getUnusedTestValue() + { + return new TestRow("unused", -1, false); + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + RowBlockBuilder blockBuilder = new RowBlockBuilder(List.of(VARCHAR, INTEGER, BOOLEAN), null, 1); + for (TestRow row : values) { + if (row == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.buildEntry(fieldBuilders -> { + if (row.name() == null) { + fieldBuilders.get(0).appendNull(); + } + else { + VARCHAR.writeString(fieldBuilders.get(0), row.name()); + } + INTEGER.writeLong(fieldBuilders.get(1), row.number()); + BOOLEAN.writeBoolean(fieldBuilders.get(2), row.flag()); + }); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + RowBlock block = (RowBlock) valueBlock; + List actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + SqlRow sqlRow = block.getRow(i); + actualValues.add(new TestRow( + (String) VARCHAR.getObjectValue(null, sqlRow.getUnderlyingFieldBlock(0), sqlRow.getUnderlyingFieldPosition(0)), + INTEGER.getInt(sqlRow.getUnderlyingFieldBlock(1), sqlRow.getUnderlyingFieldPosition(1)), + BOOLEAN.getBoolean(sqlRow.getUnderlyingFieldBlock(2), sqlRow.getUnderlyingFieldPosition(2)))); + } + } + return actualValues; + } + + public record TestRow(String name, int number, boolean flag) {} } diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java new file mode 100644 index 000000000000..031e16b81079 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestShortArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new ShortArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of((short) 10, (short) 11, (short) 12, (short) 13, (short) 14); + } + + @Override + protected Short getUnusedTestValue() + { + return (short) -1; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + ShortArrayBlockBuilder blockBuilder = new ShortArrayBlockBuilder(null, 1); + for (Short value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeShort(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) valueBlock; + List actualValues = new ArrayList<>(shortArrayBlock.getPositionCount()); + for (int i = 0; i < shortArrayBlock.getPositionCount(); i++) { + if (shortArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(shortArrayBlock.getShort(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java index 881840b351cd..5e86f7d4da6f 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java @@ -13,17 +13,24 @@ */ package io.trino.spi.block; +import com.google.common.collect.ImmutableList; import io.airlift.slice.Slices; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static io.airlift.slice.SizeOf.SIZE_OF_INT; import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.airlift.slice.Slices.utf8Slice; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Math.ceil; +import static java.util.Collections.nCopies; import static org.assertj.core.api.Assertions.assertThat; public class TestVariableWidthBlockBuilder + extends AbstractTestBlockBuilder { private static final int BLOCK_BUILDER_INSTANCE_SIZE = instanceSize(VariableWidthBlockBuilder.class); private static final int VARCHAR_VALUE_SIZE = 7; @@ -78,6 +85,116 @@ public void testBuilderProducesNullRleForNullRows() assertIsAllNulls(blockBuilder().appendNull().appendNull().build(), 2); } + @Test + public void testAppendRepeatedEmpty() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + ValueBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(Slices.EMPTY_SLICE) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 10); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(10, "")); + } + + @Test + public void testAppendRepeatedSingle() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("ab")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 1); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(ImmutableList.of("ab")); + } + + @Test + public void testAppendRepeated1Byte() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("X")) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("Y")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 3); + blockBuilder.appendRepeated(value, 3, 2); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(ImmutableList.of("X", "X", "X", "Y", "Y")); + } + + @Test + public void testAppendRepeated2Bytes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("ab")) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("Y")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 3); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(ImmutableList.of("ab", "ab", "ab")); + } + + @Test + public void testAppendRepeatedMultipleBytesOddNumberOfTimes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("abc")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 5); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(5, "abc")); + } + + @Test + public void testAppendRepeatedMultipleBytesEvenNumberOfTimes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("abc")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 6); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(6, "abc")); + } + + @Test + public void testAppendRepeatedMultipleBytesPowerOf2NumberOfTimes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("abc")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 8); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(8, "abc")); + } + private static BlockBuilder blockBuilder() { return new VariableWidthBlockBuilder(null, 10, 0); @@ -97,4 +214,62 @@ private static void assertIsAllNulls(Block block, int expectedPositionCount) assertThat(block.isNull(0)).isTrue(); } } + + private static List toStrings(VariableWidthBlock block) + { + ImmutableList.Builder list = ImmutableList.builder(); + for (int i = 0; i < block.getPositionCount(); i++) { + list.add(VARCHAR.getSlice(block, i).toStringUtf8()); + } + return list.build(); + } + + @Override + protected BlockBuilder createBlockBuilder() + { + return new VariableWidthBlockBuilder(null, 1, 100); + } + + @Override + protected List getTestValues() + { + return List.of("a", "bb", "cCc", "dddd", "eeEee"); + } + + @Override + protected String getUnusedTestValue() + { + return "unused"; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + for (String value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeEntry(utf8Slice(value)); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + VariableWidthBlock block = (VariableWidthBlock) valueBlock; + List actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(block.getSlice(i).toStringUtf8()); + } + } + return actualValues; + } }