diff --git a/core/trino-main/src/main/java/io/trino/operator/aggregation/multimapagg/MultimapAggregationFunction.java b/core/trino-main/src/main/java/io/trino/operator/aggregation/multimapagg/MultimapAggregationFunction.java index da1c9fa7e03d..baa46084d132 100644 --- a/core/trino-main/src/main/java/io/trino/operator/aggregation/multimapagg/MultimapAggregationFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/aggregation/multimapagg/MultimapAggregationFunction.java @@ -15,7 +15,7 @@ import io.trino.array.ObjectBigArray; import io.trino.operator.aggregation.NullablePosition; -import io.trino.operator.aggregation.TypedSet; +import io.trino.operator.scalar.BlockSet; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.block.MapBlockBuilder; @@ -37,7 +37,6 @@ import io.trino.type.BlockTypeOperators.BlockPositionHashCode; import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.NULLABLE_RETURN; @@ -96,7 +95,7 @@ public static void output( ObjectBigArray valueArrayBlockBuilders = new ObjectBigArray<>(); valueArrayBlockBuilders.ensureCapacity(state.getEntryCount()); BlockBuilder distinctKeyBlockBuilder = keyType.createBlockBuilder(null, state.getEntryCount(), expectedValueSize(keyType, 100)); - TypedSet keySet = createDistinctTypedSet(keyType, keyDistinctFrom, keyHashCode, state.getEntryCount(), "multimap_agg"); + BlockSet keySet = new BlockSet(keyType, keyDistinctFrom, keyHashCode, state.getEntryCount()); state.forEach((key, value, keyValueIndex) -> { // Merge values of the same key into an array diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayDistinctFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayDistinctFunction.java index 2d3a22ec0408..3f0e991023d8 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayDistinctFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayDistinctFunction.java @@ -13,24 +13,22 @@ */ package io.trino.operator.scalar; -import com.google.common.collect.ImmutableList; -import io.trino.operator.aggregation.TypedSet; -import io.trino.spi.PageBuilder; import io.trino.spi.block.Block; -import io.trino.spi.block.BlockBuilder; +import io.trino.spi.block.BufferedArrayValueBuilder; import io.trino.spi.function.Convention; import io.trino.spi.function.Description; import io.trino.spi.function.OperatorDependency; import io.trino.spi.function.ScalarFunction; import io.trino.spi.function.SqlType; import io.trino.spi.function.TypeParameter; +import io.trino.spi.type.ArrayType; import io.trino.spi.type.Type; import io.trino.type.BlockTypeOperators.BlockPositionHashCode; import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; +import static io.trino.operator.scalar.BlockSet.MAX_FUNCTION_MEMORY; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.function.OperatorType.HASH_CODE; @@ -42,12 +40,12 @@ public final class ArrayDistinctFunction { public static final String NAME = "array_distinct"; - private final PageBuilder pageBuilder; + private final BufferedArrayValueBuilder arrayValueBuilder; @TypeParameter("E") public ArrayDistinctFunction(@TypeParameter("E") Type elementType) { - pageBuilder = new PageBuilder(ImmutableList.of(elementType)); + arrayValueBuilder = BufferedArrayValueBuilder.createBuffered(new ArrayType(elementType)); } @TypeParameter("E") @@ -76,27 +74,19 @@ public Block distinct( return array.getSingleValueBlock(0); } - if (pageBuilder.isFull()) { - pageBuilder.reset(); - } - - BlockBuilder distinctElementsBlockBuilder = pageBuilder.getBlockBuilder(0); - TypedSet distinctElements = createDistinctTypedSet( + BlockSet distinctElements = new BlockSet( type, elementIsDistinctFrom, elementHashCode, - distinctElementsBlockBuilder, - array.getPositionCount(), - "array_distinct"); + array.getPositionCount()); for (int i = 0; i < array.getPositionCount(); i++) { distinctElements.add(array, i); } - pageBuilder.declarePositions(distinctElements.size()); - return distinctElementsBlockBuilder.getRegion( - distinctElementsBlockBuilder.getPositionCount() - distinctElements.size(), - distinctElements.size()); + return arrayValueBuilder.build( + distinctElements.size(), + blockBuilder -> distinctElements.getAllWithSizeLimit(blockBuilder, "array_distinct", MAX_FUNCTION_MEMORY)); } @SqlType("array(bigint)") @@ -106,36 +96,23 @@ public Block bigintDistinct(@SqlType("array(bigint)") Block array) return array; } - boolean containsNull = false; - LongSet set = new LongOpenHashSet(array.getPositionCount()); - int distinctCount = 0; - - if (pageBuilder.isFull()) { - pageBuilder.reset(); - } - - BlockBuilder distinctElementBlockBuilder = pageBuilder.getBlockBuilder(0); - for (int i = 0; i < array.getPositionCount(); i++) { - if (array.isNull(i)) { - if (!containsNull) { - containsNull = true; - distinctElementBlockBuilder.appendNull(); - distinctCount++; + return arrayValueBuilder.build(array.getPositionCount(), distinctElementBlockBuilder -> { + boolean containsNull = false; + LongSet set = new LongOpenHashSet(array.getPositionCount()); + + for (int i = 0; i < array.getPositionCount(); i++) { + if (array.isNull(i)) { + if (!containsNull) { + containsNull = true; + distinctElementBlockBuilder.appendNull(); + } + continue; + } + long value = BIGINT.getLong(array, i); + if (set.add(value)) { + BIGINT.writeLong(distinctElementBlockBuilder, value); } - continue; - } - long value = BIGINT.getLong(array, i); - if (!set.contains(value)) { - set.add(value); - distinctCount++; - BIGINT.appendTo(array, i, distinctElementBlockBuilder); } - } - - pageBuilder.declarePositions(distinctCount); - - return distinctElementBlockBuilder.getRegion( - distinctElementBlockBuilder.getPositionCount() - distinctCount, - distinctCount); + }); } } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayExceptFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayExceptFunction.java index 54d7e5afa385..ba9ad4842e19 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayExceptFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayExceptFunction.java @@ -13,7 +13,6 @@ */ package io.trino.operator.scalar; -import io.trino.operator.aggregation.TypedSet; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.function.Convention; @@ -26,7 +25,6 @@ import io.trino.type.BlockTypeOperators.BlockPositionHashCode; import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.function.OperatorType.HASH_CODE; @@ -60,13 +58,13 @@ public static Block except( return leftArray; } - TypedSet typedSet = createDistinctTypedSet(type, isDistinctOperator, elementHashCode, leftPositionCount, "array_except"); - BlockBuilder distinctElementBlockBuilder = type.createBlockBuilder(null, leftPositionCount); + BlockSet set = new BlockSet(type, isDistinctOperator, elementHashCode, rightPositionCount + leftPositionCount); for (int i = 0; i < rightPositionCount; i++) { - typedSet.add(rightArray, i); + set.add(rightArray, i); } + BlockBuilder distinctElementBlockBuilder = type.createBlockBuilder(null, leftPositionCount); for (int i = 0; i < leftPositionCount; i++) { - if (typedSet.add(leftArray, i)) { + if (set.add(leftArray, i)) { type.appendTo(leftArray, i, distinctElementBlockBuilder); } } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayIntersectFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayIntersectFunction.java index fd113424c56c..4c18f3d2a877 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayIntersectFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayIntersectFunction.java @@ -13,22 +13,20 @@ */ package io.trino.operator.scalar; -import com.google.common.collect.ImmutableList; -import io.trino.operator.aggregation.TypedSet; -import io.trino.spi.PageBuilder; import io.trino.spi.block.Block; -import io.trino.spi.block.BlockBuilder; +import io.trino.spi.block.BufferedArrayValueBuilder; import io.trino.spi.function.Convention; import io.trino.spi.function.Description; import io.trino.spi.function.OperatorDependency; import io.trino.spi.function.ScalarFunction; import io.trino.spi.function.SqlType; import io.trino.spi.function.TypeParameter; +import io.trino.spi.type.ArrayType; import io.trino.spi.type.Type; import io.trino.type.BlockTypeOperators.BlockPositionHashCode; import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; +import static io.trino.operator.scalar.BlockSet.MAX_FUNCTION_MEMORY; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.function.OperatorType.HASH_CODE; @@ -38,12 +36,12 @@ @Description("Intersects elements of the two given arrays") public final class ArrayIntersectFunction { - private final PageBuilder pageBuilder; + private final BufferedArrayValueBuilder arrayValueBuilder; @TypeParameter("E") public ArrayIntersectFunction(@TypeParameter("E") Type elementType) { - pageBuilder = new PageBuilder(ImmutableList.of(elementType)); + arrayValueBuilder = BufferedArrayValueBuilder.createBuffered(new ArrayType(elementType)); } @TypeParameter("E") @@ -74,27 +72,21 @@ public Block intersect( return rightArray; } - if (pageBuilder.isFull()) { - pageBuilder.reset(); - } - - TypedSet rightTypedSet = createDistinctTypedSet(type, elementIsDistinctFrom, elementHashCode, rightPositionCount, "array_intersect"); + BlockSet rightSet = new BlockSet(type, elementIsDistinctFrom, elementHashCode, rightPositionCount); for (int i = 0; i < rightPositionCount; i++) { - rightTypedSet.add(rightArray, i); + rightSet.add(rightArray, i); } - BlockBuilder blockBuilder = pageBuilder.getBlockBuilder(0); - // The intersected set can have at most rightPositionCount elements - TypedSet intersectTypedSet = createDistinctTypedSet(type, elementIsDistinctFrom, elementHashCode, blockBuilder, rightPositionCount, "array_intersect"); + BlockSet intersectSet = new BlockSet(type, elementIsDistinctFrom, elementHashCode, rightSet.size()); for (int i = 0; i < leftPositionCount; i++) { - if (rightTypedSet.contains(leftArray, i)) { - intersectTypedSet.add(leftArray, i); + if (rightSet.contains(leftArray, i)) { + intersectSet.add(leftArray, i); } } - pageBuilder.declarePositions(intersectTypedSet.size()); - - return blockBuilder.getRegion(blockBuilder.getPositionCount() - intersectTypedSet.size(), intersectTypedSet.size()); + return arrayValueBuilder.build( + intersectSet.size(), + blockBuilder -> intersectSet.getAllWithSizeLimit(blockBuilder, "array_intersect", MAX_FUNCTION_MEMORY)); } } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayUnionFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayUnionFunction.java index ac21673bce25..3741ea9c9ab2 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayUnionFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/ArrayUnionFunction.java @@ -13,7 +13,6 @@ */ package io.trino.operator.scalar; -import io.trino.operator.aggregation.TypedSet; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.function.Convention; @@ -30,7 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; +import static io.trino.operator.scalar.BlockSet.MAX_FUNCTION_MEMORY; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.function.OperatorType.HASH_CODE; @@ -58,26 +57,23 @@ public static Block union( @SqlType("array(E)") Block leftArray, @SqlType("array(E)") Block rightArray) { - int leftArrayCount = leftArray.getPositionCount(); - int rightArrayCount = rightArray.getPositionCount(); - BlockBuilder distinctElementBlockBuilder = type.createBlockBuilder(null, leftArrayCount + rightArrayCount); - TypedSet typedSet = createDistinctTypedSet( + BlockSet set = new BlockSet( type, isDistinctOperator, elementHashCode, - distinctElementBlockBuilder, - leftArrayCount + rightArrayCount, - "array_union"); + leftArray.getPositionCount() + rightArray.getPositionCount()); for (int i = 0; i < leftArray.getPositionCount(); i++) { - typedSet.add(leftArray, i); + set.add(leftArray, i); } for (int i = 0; i < rightArray.getPositionCount(); i++) { - typedSet.add(rightArray, i); + set.add(rightArray, i); } - return distinctElementBlockBuilder.build(); + BlockBuilder blockBuilder = type.createBlockBuilder(null, set.size()); + set.getAllWithSizeLimit(blockBuilder, "array_union", MAX_FUNCTION_MEMORY); + return blockBuilder.build(); } @SqlType("array(bigint)") diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/BlockSet.java b/core/trino-main/src/main/java/io/trino/operator/scalar/BlockSet.java new file mode 100644 index 000000000000..c595dabcca7c --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/BlockSet.java @@ -0,0 +1,213 @@ +/* + * 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.operator.scalar; + +import io.airlift.units.DataSize; +import io.trino.spi.TrinoException; +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.type.Type; +import io.trino.type.BlockTypeOperators.BlockPositionHashCode; +import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; + +import java.util.Arrays; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static io.trino.spi.StandardErrorCode.EXCEEDED_FUNCTION_MEMORY_LIMIT; +import static it.unimi.dsi.fastutil.HashCommon.arraySize; +import static java.lang.Math.toIntExact; +import static java.util.Objects.requireNonNull; + +/** + * A set of values stored in preexisting blocks. The data is not copied out of the + * blocks, and instead a direct reference is kept. This means that all data in a block + * is retained (including non-distinct values), so this works best when processing + * preexisting blocks in a single code block. Care should be taken when using this + * across multiple calls, as the memory will not be freed until the BlockSet is freed. + *

+ * BlockSet does not support rehashing, so the maximum size must be known up front. + */ +public class BlockSet +{ + public static final DataSize MAX_FUNCTION_MEMORY = DataSize.of(4, MEGABYTE); + + private static final float FILL_RATIO = 0.75f; + private static final int EMPTY_SLOT = -1; + + private final Type elementType; + private final BlockPositionIsDistinctFrom elementDistinctFromOperator; + private final BlockPositionHashCode elementHashCodeOperator; + + private final int[] blockPositionByHash; + + private final Block[] elementBlocks; + private final int[] elementPositions; + + private int size; + + private final int maximumSize; + private final int hashMask; + + private boolean containsNullElement; + + public BlockSet( + Type elementType, + BlockPositionIsDistinctFrom elementDistinctFromOperator, + BlockPositionHashCode elementHashCodeOperator, + int maximumSize) + { + checkArgument(maximumSize >= 0, "maximumSize must not be negative"); + this.elementType = requireNonNull(elementType, "elementType is null"); + this.elementDistinctFromOperator = requireNonNull(elementDistinctFromOperator, "elementDistinctFromOperator is null"); + this.elementHashCodeOperator = requireNonNull(elementHashCodeOperator, "elementHashCodeOperator is null"); + this.maximumSize = maximumSize; + + int hashCapacity = arraySize(maximumSize, FILL_RATIO); + this.hashMask = hashCapacity - 1; + + blockPositionByHash = new int[hashCapacity]; + Arrays.fill(blockPositionByHash, EMPTY_SLOT); + + this.elementBlocks = new Block[maximumSize]; + this.elementPositions = new int[maximumSize]; + + this.containsNullElement = false; + } + + /** + * Does this set contain the value? + */ + public boolean contains(Block block, int position) + { + requireNonNull(block, "block must not be null"); + checkArgument(position >= 0, "position must be >= 0"); + + if (block.isNull(position)) { + return containsNullElement; + } + return positionOf(block, position) != EMPTY_SLOT; + } + + /** + * Add the value to this set. + * + * @return {@code true} if the value was added, or {@code false} if it was + * already in this set. + */ + public boolean add(Block block, int position) + { + requireNonNull(block, "block must not be null"); + checkArgument(position >= 0, "position must be >= 0"); + + // containsNullElement flag is maintained so contains() method can have a shortcut for null value + if (block.isNull(position)) { + if (containsNullElement) { + return false; + } + containsNullElement = true; + } + + int hashPosition = getHashPositionOfElement(block, position); + if (blockPositionByHash[hashPosition] == EMPTY_SLOT) { + addNewElement(hashPosition, block, position); + return true; + } + return false; + } + + /** + * Returns the number of elements in this set. + */ + public int size() + { + return size; + } + + /** + * Return the position of the value within this set, or -1 if the value is not in this set. + * This method can not get the position of a null value, and an exception will be thrown in that case. + * + * @throws IllegalArgumentException if the position is null + */ + public int positionOf(Block block, int position) + { + return blockPositionByHash[getHashPositionOfElement(block, position)]; + } + + /** + * Writes all values to the block builder checking the memory limit after each element is added. + */ + public void getAllWithSizeLimit(BlockBuilder blockBuilder, String functionName, DataSize maxFunctionMemory) + { + long initialSize = blockBuilder.getSizeInBytes(); + long maxBlockMemoryInBytes = toIntExact(maxFunctionMemory.toBytes()); + for (int i = 0; i < size; i++) { + elementType.appendTo(elementBlocks[i], elementPositions[i], blockBuilder); + if (blockBuilder.getSizeInBytes() - initialSize > maxBlockMemoryInBytes) { + throw new TrinoException( + EXCEEDED_FUNCTION_MEMORY_LIMIT, + "The input to %s is too large. More than %s of memory is needed to hold the output hash set.".formatted(functionName, maxFunctionMemory)); + } + } + } + + /** + * Get hash slot position of the element. If the element is not in the set, return the position + * where the element should be inserted. + */ + private int getHashPositionOfElement(Block block, int position) + { + int hashPosition = getMaskedHash(elementHashCodeOperator.hashCodeNullSafe(block, position)); + while (true) { + int blockPosition = blockPositionByHash[hashPosition]; + if (blockPosition == EMPTY_SLOT) { + // Doesn't have this element + return hashPosition; + } + if (isNotDistinct(blockPosition, block, position)) { + // Already has this element + return hashPosition; + } + + hashPosition = getMaskedHash(hashPosition + 1); + } + } + + private void addNewElement(int hashPosition, Block block, int position) + { + checkState(size < maximumSize, "BlockSet is full"); + + elementBlocks[size] = block; + elementPositions[size] = position; + + blockPositionByHash[hashPosition] = size; + size++; + } + + private boolean isNotDistinct(int leftPosition, Block rightBlock, int rightPosition) + { + return !elementDistinctFromOperator.isDistinctFrom( + elementBlocks[leftPosition], + elementPositions[leftPosition], + rightBlock, + rightPosition); + } + + private int getMaskedHash(long rawHash) + { + return (int) (rawHash & hashMask); + } +} diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/MapConcatFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/MapConcatFunction.java index 23ec1b58e104..e83dc69e12c6 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/MapConcatFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/MapConcatFunction.java @@ -15,7 +15,6 @@ import io.trino.annotation.UsedByGeneratedCode; import io.trino.metadata.SqlScalarFunction; -import io.trino.operator.aggregation.TypedSet; import io.trino.spi.TrinoException; import io.trino.spi.block.Block; import io.trino.spi.block.BufferedMapValueBuilder; @@ -34,7 +33,6 @@ import java.lang.invoke.MethodHandles; import java.util.Optional; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.NEVER_NULL; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; @@ -115,11 +113,11 @@ public static Object createMapState(MapType mapType) @UsedByGeneratedCode public static Block mapConcat(MapType mapType, BlockPositionIsDistinctFrom keysDistinctOperator, BlockPositionHashCode keyHashCode, Object state, Block[] maps) { - int entries = 0; + int maxEntries = 0; int lastMapIndex = maps.length - 1; int firstMapIndex = lastMapIndex; for (int i = 0; i < maps.length; i++) { - entries += maps[i].getPositionCount(); + maxEntries += maps[i].getPositionCount() / 2; if (maps[i].getPositionCount() > 0) { lastMapIndex = i; firstMapIndex = min(firstMapIndex, i); @@ -133,15 +131,14 @@ public static Block mapConcat(MapType mapType, BlockPositionIsDistinctFrom keysD BufferedMapValueBuilder mapValueBuilder = (BufferedMapValueBuilder) state; - // TODO: we should move TypedSet into user state as well Type keyType = mapType.getKeyType(); Type valueType = mapType.getValueType(); - TypedSet typedSet = createDistinctTypedSet(keyType, keysDistinctOperator, keyHashCode, entries / 2, FUNCTION_NAME); - return mapValueBuilder.build(entries / 2, (keyBuilder, valueBuilder) -> { + BlockSet set = new BlockSet(keyType, keysDistinctOperator, keyHashCode, maxEntries); + return mapValueBuilder.build(maxEntries, (keyBuilder, valueBuilder) -> { // the last map Block map = maps[last]; for (int i = 0; i < map.getPositionCount(); i += 2) { - typedSet.add(map, i); + set.add(map, i); keyType.appendTo(map, i, keyBuilder); valueType.appendTo(map, i + 1, valueBuilder); } @@ -149,7 +146,7 @@ public static Block mapConcat(MapType mapType, BlockPositionIsDistinctFrom keysD for (int idx = last - 1; idx > first; idx--) { map = maps[idx]; for (int i = 0; i < map.getPositionCount(); i += 2) { - if (typedSet.add(map, i)) { + if (set.add(map, i)) { keyType.appendTo(map, i, keyBuilder); valueType.appendTo(map, i + 1, valueBuilder); } @@ -158,7 +155,7 @@ public static Block mapConcat(MapType mapType, BlockPositionIsDistinctFrom keysD // the first map map = maps[first]; for (int i = 0; i < map.getPositionCount(); i += 2) { - if (!typedSet.contains(map, i)) { + if (!set.contains(map, i)) { keyType.appendTo(map, i, keyBuilder); valueType.appendTo(map, i + 1, valueBuilder); } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/MapToMapCast.java b/core/trino-main/src/main/java/io/trino/operator/scalar/MapToMapCast.java index c5924a79eb43..2a9e8949d0e6 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/MapToMapCast.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/MapToMapCast.java @@ -17,7 +17,6 @@ import io.airlift.slice.Slice; import io.trino.annotation.UsedByGeneratedCode; import io.trino.metadata.SqlScalarFunction; -import io.trino.operator.aggregation.TypedSet; import io.trino.spi.TrinoException; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; @@ -40,7 +39,6 @@ import java.lang.invoke.MethodHandles; import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.block.MapValueBuilder.buildMapValue; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; @@ -251,7 +249,6 @@ public static Block mapCast( { MapType mapType = (MapType) targetType; Type toKeyType = mapType.getKeyType(); - TypedSet resultKeys = createDistinctTypedSet(toKeyType, keyDistinctOperator, keyHashCode, fromMap.getPositionCount() / 2, "map-to-map cast"); // Cast the keys into a new block BlockBuilder tempKeyBlockBuilder = toKeyType.createBlockBuilder(null, fromMap.getPositionCount() / 2); @@ -267,6 +264,7 @@ public static Block mapCast( // TODO this should build the value block directly and then construct a single map from the two blocks return buildMapValue(mapType, fromMap.getPositionCount() / 2, (keyBuilder, valueBuilder) -> { + BlockSet resultKeys = new BlockSet(toKeyType, keyDistinctOperator, keyHashCode, fromMap.getPositionCount() / 2); for (int i = 0; i < fromMap.getPositionCount(); i += 2) { if (resultKeys.add(keyBlock, i / 2)) { toKeyType.appendTo(keyBlock, i / 2, keyBuilder); diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java b/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java index 8f9108629c4f..2f2b6db70099 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java @@ -20,7 +20,6 @@ import com.google.common.primitives.SignedBytes; import io.airlift.slice.Slice; import io.trino.metadata.SqlScalarFunction; -import io.trino.operator.aggregation.TypedSet; import io.trino.spi.TrinoException; import io.trino.spi.block.Block; import io.trino.spi.function.Convention; @@ -35,8 +34,8 @@ import io.trino.spi.type.Decimals; import io.trino.spi.type.Int128; import io.trino.spi.type.StandardTypes; -import io.trino.type.BlockTypeOperators.BlockPositionEqual; import io.trino.type.BlockTypeOperators.BlockPositionHashCode; +import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; import io.trino.type.Constraint; import org.apache.commons.math3.distribution.BetaDistribution; import org.apache.commons.math3.special.Erf; @@ -46,14 +45,13 @@ import java.util.concurrent.ThreadLocalRandom; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.operator.aggregation.TypedSet.createEqualityTypedSet; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.NULLABLE_RETURN; -import static io.trino.spi.function.OperatorType.EQUAL; import static io.trino.spi.function.OperatorType.HASH_CODE; +import static io.trino.spi.function.OperatorType.IS_DISTINCT_FROM; import static io.trino.spi.type.Decimals.longTenToNth; import static io.trino.spi.type.Decimals.overflows; import static io.trino.spi.type.DoubleType.DOUBLE; @@ -1361,9 +1359,9 @@ public static long widthBucket(@SqlType(StandardTypes.DOUBLE) double operand, @S @SqlType(StandardTypes.DOUBLE) public static Double cosineSimilarity( @OperatorDependency( - operator = EQUAL, + operator = IS_DISTINCT_FROM, argumentTypes = {"varchar", "varchar"}, - convention = @Convention(arguments = {BLOCK_POSITION, BLOCK_POSITION}, result = NULLABLE_RETURN)) BlockPositionEqual varcharEqual, + convention = @Convention(arguments = {BLOCK_POSITION, BLOCK_POSITION}, result = NULLABLE_RETURN)) BlockPositionIsDistinctFrom varcharDistinct, @OperatorDependency( operator = HASH_CODE, argumentTypes = "varchar", @@ -1378,14 +1376,14 @@ public static Double cosineSimilarity( return null; } - double dotProduct = mapDotProduct(varcharEqual, varcharHashCode, leftMap, rightMap); + double dotProduct = mapDotProduct(varcharDistinct, varcharHashCode, leftMap, rightMap); return dotProduct / (normLeftMap * normRightMap); } - private static double mapDotProduct(BlockPositionEqual varcharEqual, BlockPositionHashCode varcharHashCode, Block leftMap, Block rightMap) + private static double mapDotProduct(BlockPositionIsDistinctFrom varcharDistinct, BlockPositionHashCode varcharHashCode, Block leftMap, Block rightMap) { - TypedSet rightMapKeys = createEqualityTypedSet(VARCHAR, varcharEqual, varcharHashCode, rightMap.getPositionCount(), "cosine_similarity"); + BlockSet rightMapKeys = new BlockSet(VARCHAR, varcharDistinct, varcharHashCode, rightMap.getPositionCount() / 2); for (int i = 0; i < rightMap.getPositionCount(); i += 2) { rightMapKeys.add(rightMap, i); diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/MultimapFromEntriesFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/MultimapFromEntriesFunction.java index d3a4e6c9a40a..cfc8f17ce615 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/MultimapFromEntriesFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/MultimapFromEntriesFunction.java @@ -14,7 +14,6 @@ package io.trino.operator.scalar; import com.google.common.collect.ImmutableList; -import io.trino.operator.aggregation.TypedSet; import io.trino.spi.TrinoException; import io.trino.spi.block.ArrayBlockBuilder; import io.trino.spi.block.Block; @@ -36,7 +35,6 @@ import it.unimi.dsi.fastutil.ints.IntList; import static com.google.common.base.Verify.verify; -import static io.trino.operator.aggregation.TypedSet.createDistinctTypedSet; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.BLOCK_POSITION; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; @@ -47,7 +45,6 @@ @Description("Construct a multimap from an array of entries") public final class MultimapFromEntriesFunction { - private static final String NAME = "multimap_from_entries"; private static final int INITIAL_ENTRY_COUNT = 128; private final BufferedMapValueBuilder mapValueBuilder; @@ -85,7 +82,7 @@ public Block multimapFromEntries( if (entryCount > entryIndicesList.length) { initializeEntryIndicesList(entryCount); } - TypedSet keySet = createDistinctTypedSet(keyType, keysDistinctOperator, keyHashCode, entryCount, NAME); + BlockSet keySet = new BlockSet(keyType, keysDistinctOperator, keyHashCode, entryCount); for (int i = 0; i < entryCount; i++) { if (mapEntries.isNull(i)) { diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkArrayDistinct.java b/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkArrayDistinct.java index 638ecc7cc8da..021a84698897 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkArrayDistinct.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkArrayDistinct.java @@ -18,7 +18,6 @@ import io.trino.metadata.InternalFunctionBundle; import io.trino.metadata.TestingFunctionResolution; import io.trino.operator.DriverYieldSignal; -import io.trino.operator.aggregation.TypedSet; import io.trino.operator.project.PageProcessor; import io.trino.spi.Page; import io.trino.spi.block.ArrayBlockBuilder; @@ -34,8 +33,8 @@ import io.trino.sql.relational.RowExpression; import io.trino.sql.tree.QualifiedName; import io.trino.type.BlockTypeOperators; -import io.trino.type.BlockTypeOperators.BlockPositionEqual; import io.trino.type.BlockTypeOperators.BlockPositionHashCode; +import io.trino.type.BlockTypeOperators.BlockPositionIsDistinctFrom; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -57,7 +56,6 @@ import static com.google.common.base.Verify.verify; import static io.trino.jmh.Benchmarks.benchmark; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; -import static io.trino.operator.aggregation.TypedSet.createEqualityTypedSet; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.relational.Expressions.field; @@ -77,7 +75,7 @@ public class BenchmarkArrayDistinct private static final int NUM_TYPES = 1; private static final List TYPES = ImmutableList.of(VARCHAR); private static final BlockTypeOperators BLOCK_TYPE_OPERATORS = new BlockTypeOperators(new TypeOperators()); - private static final BlockPositionEqual EQUAL_OPERATOR = BLOCK_TYPE_OPERATORS.getEqualOperator(VARCHAR); + private static final BlockPositionIsDistinctFrom DISTINCT_FROM_OPERATOR = BLOCK_TYPE_OPERATORS.getDistinctFromOperator(VARCHAR); private static final BlockPositionHashCode HASH_CODE_OPERATOR = BLOCK_TYPE_OPERATORS.getHashCodeOperator(VARCHAR); static { @@ -179,11 +177,10 @@ public static Block oldArrayDistinct(@SqlType("array(varchar)") Block array) return array; } - TypedSet typedSet = createEqualityTypedSet(VARCHAR, EQUAL_OPERATOR, HASH_CODE_OPERATOR, array.getPositionCount(), "old_array_distinct"); + BlockSet set = new BlockSet(VARCHAR, DISTINCT_FROM_OPERATOR, HASH_CODE_OPERATOR, array.getPositionCount()); BlockBuilder distinctElementBlockBuilder = VARCHAR.createBlockBuilder(null, array.getPositionCount()); for (int i = 0; i < array.getPositionCount(); i++) { - if (!typedSet.contains(array, i)) { - typedSet.add(array, i); + if (set.add(array, i)) { VARCHAR.appendTo(array, i, distinctElementBlockBuilder); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java new file mode 100644 index 000000000000..98c31e57e002 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java @@ -0,0 +1,265 @@ +/* + * 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.operator.scalar; + +import com.google.common.collect.ImmutableList; +import io.airlift.units.DataSize; +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.type.Type; +import io.trino.spi.type.TypeOperators; +import io.trino.type.BlockTypeOperators; +import org.testng.annotations.Test; + +import java.util.HashSet; +import java.util.Set; + +import static io.airlift.slice.Slices.utf8Slice; +import static io.airlift.units.DataSize.Unit.KILOBYTE; +import static io.trino.block.BlockAssertions.createEmptyLongsBlock; +import static io.trino.block.BlockAssertions.createLongSequenceBlock; +import static io.trino.block.BlockAssertions.createLongsBlock; +import static io.trino.spi.StandardErrorCode.EXCEEDED_FUNCTION_MEMORY_LIMIT; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static java.util.Collections.nCopies; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class TestBlockSet +{ + private static final BlockTypeOperators BLOCK_TYPE_OPERATORS = new BlockTypeOperators(new TypeOperators()); + private static final String FUNCTION_NAME = "typed_set_test"; + + @Test + public void testConstructor() + { + for (int i = -2; i <= -1; i++) { + int expectedSize = i; + assertThatThrownBy(() -> createBlockSet(BIGINT, expectedSize)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("maximumSize must not be negative"); + } + + assertThatThrownBy(() -> new BlockSet(null, null, null, 1)) + .isInstanceOfAny(NullPointerException.class, IllegalArgumentException.class); + } + + @Test + public void testGetElementPosition() + { + int elementCount = 100; + BlockSet blockSet = createBlockSet(BIGINT, elementCount); + BlockBuilder blockBuilder = BIGINT.createFixedSizeBlockBuilder(elementCount); + for (int i = 0; i < elementCount; i++) { + BIGINT.writeLong(blockBuilder, i); + blockSet.add(blockBuilder, i); + } + + assertEquals(blockSet.size(), elementCount); + + for (int j = 0; j < blockBuilder.getPositionCount(); j++) { + assertEquals(blockSet.positionOf(blockBuilder, j), j); + } + } + + @Test + public void testGetElementPositionWithNull() + { + int elementCount = 100; + BlockSet blockSet = createBlockSet(BIGINT, elementCount); + BlockBuilder blockBuilder = BIGINT.createFixedSizeBlockBuilder(elementCount); + for (int i = 0; i < elementCount; i++) { + if (i % 10 == 0) { + blockBuilder.appendNull(); + } + else { + BIGINT.writeLong(blockBuilder, i); + } + blockSet.add(blockBuilder, i); + } + + // The internal elementBlock and hashtable of the blockSet should contain + // all distinct non-null elements plus one null + assertEquals(blockSet.size(), elementCount - elementCount / 10 + 1); + + int nullCount = 0; + for (int j = 0; j < blockBuilder.getPositionCount(); j++) { + // The null is only added to blockSet once, so the internal elementBlock subscript is shifted by nullCountMinusOne + if (!blockBuilder.isNull(j)) { + assertEquals(blockSet.positionOf(blockBuilder, j), j - nullCount + 1); + } + else { + // The first null added to blockSet is at position 0 + assertEquals(blockSet.positionOf(blockBuilder, j), 0); + nullCount++; + } + } + } + + @Test + public void testMaxSize() + { + for (int maxSize : ImmutableList.of(0, 1, 10, 100, 1000)) { + BlockSet blockSet = createBlockSet(BIGINT, maxSize); + for (int i = 0; i < maxSize; i++) { + assertThat(blockSet.add(toBlock(i == 20 ? null : (long) i), 0)).isTrue(); + assertThat(blockSet.size()).isEqualTo(i + 1); + } + + assertThatThrownBy(() -> blockSet.add(toBlock((long) maxSize), 0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("BlockSet is full"); + assertThat(blockSet.size()).isEqualTo(maxSize); + + if (maxSize < 20) { + assertThatThrownBy(() -> blockSet.add(toBlock(null), 0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("BlockSet is full"); + assertThat(blockSet.size()).isEqualTo(maxSize); + } + + for (int i = 0; i < maxSize; i++) { + assertThat(blockSet.add(toBlock(i == 20 ? null : (long) i), 0)).isFalse(); + } + } + } + + private static Block toBlock(Long value) + { + BlockBuilder blockBuilder = BIGINT.createFixedSizeBlockBuilder(1); + if (value == null) { + blockBuilder.appendNull(); + } + else { + BIGINT.writeLong(blockBuilder, value); + } + return blockBuilder.build(); + } + + @Test + public void testGetElementPositionRandom() + { + BlockBuilder keys = VARCHAR.createBlockBuilder(null, 5); + VARCHAR.writeSlice(keys, utf8Slice("hello")); + VARCHAR.writeSlice(keys, utf8Slice("bye")); + VARCHAR.writeSlice(keys, utf8Slice("abc")); + + BlockSet set = createBlockSet(VARCHAR, 4); + for (int i = 0; i < keys.getPositionCount(); i++) { + set.add(keys, i); + } + + BlockBuilder values = VARCHAR.createBlockBuilder(null, 5); + VARCHAR.writeSlice(values, utf8Slice("bye")); + VARCHAR.writeSlice(values, utf8Slice("abc")); + VARCHAR.writeSlice(values, utf8Slice("hello")); + VARCHAR.writeSlice(values, utf8Slice("bad")); + values.appendNull(); + + assertEquals(set.positionOf(values, 4), -1); + assertEquals(set.positionOf(values, 2), 0); + assertEquals(set.positionOf(values, 1), 2); + assertEquals(set.positionOf(values, 0), 1); + assertFalse(set.contains(values, 3)); + + set.add(values, 4); + assertTrue(set.contains(values, 4)); + } + + @Test + public void testBigintSimpleBlockSet() + { + testBigint(createEmptyLongsBlock()); + testBigint(createLongsBlock(1L)); + testBigint(createLongsBlock(1L, 2L, 3L)); + testBigint(createLongsBlock(1L, 2L, 3L, 1L, 2L, 3L)); + testBigint(createLongsBlock(1L, null, 3L)); + testBigint(createLongsBlock(null, null, null)); + testBigint(createLongSequenceBlock(0, 100)); + testBigint(createLongSequenceBlock(-100, 100)); + testBigint(createLongsBlock(nCopies(1, null))); + testBigint(createLongsBlock(nCopies(100, null))); + testBigint(createLongsBlock(nCopies(2000, null))); + testBigint(createLongsBlock(nCopies(2000, 0L))); + } + + private static void testBigint(Block longBlock) + { + BlockSet blockSet = createBlockSet(BIGINT, longBlock.getPositionCount()); + Set set = new HashSet<>(); + for (int blockPosition = 0; blockPosition < longBlock.getPositionCount(); blockPosition++) { + long number = BIGINT.getLong(longBlock, blockPosition); + assertEquals(blockSet.contains(longBlock, blockPosition), set.contains(number)); + assertEquals(blockSet.size(), set.size()); + + set.add(number); + blockSet.add(longBlock, blockPosition); + + assertEquals(blockSet.contains(longBlock, blockPosition), set.contains(number)); + assertEquals(blockSet.size(), set.size()); + } + } + + @Test + public void testMemoryExceeded() + { + DataSize maxSize = DataSize.of(20, KILOBYTE); + BlockBuilder blockBuilder = BIGINT.createFixedSizeBlockBuilder(1024); + for (int i = 0; blockBuilder.getSizeInBytes() < maxSize.toBytes() + 8; i++) { + BIGINT.writeLong(blockBuilder, i); + } + Block block = blockBuilder.build(); + + BlockSet blockSet = createBlockSet(BIGINT, block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + blockSet.add(block, i); + } + // blockSet should contain all positions + assertThat(blockSet.size()).isEqualTo(block.getPositionCount()); + + // getting all blocks should fail + BlockBuilder testOutput = BIGINT.createFixedSizeBlockBuilder(1024); + assertTrinoExceptionThrownBy(() -> blockSet.getAllWithSizeLimit(testOutput, FUNCTION_NAME, maxSize)) + .hasErrorCode(EXCEEDED_FUNCTION_MEMORY_LIMIT) + .hasMessageContaining(FUNCTION_NAME); + + // blockBuilder should not contain all positions + int actualPositionsWritten = testOutput.getPositionCount(); + assertThat(actualPositionsWritten).isLessThan(block.getPositionCount()); + + // writing to the same block builder, should fail with the same count + assertTrinoExceptionThrownBy(() -> blockSet.getAllWithSizeLimit(testOutput, FUNCTION_NAME, maxSize)) + .hasErrorCode(EXCEEDED_FUNCTION_MEMORY_LIMIT) + .hasMessageContaining(FUNCTION_NAME); + assertThat(testOutput.getPositionCount()).isEqualTo(actualPositionsWritten * 2); + + // writing with a higher limit should work + blockSet.getAllWithSizeLimit(testOutput, FUNCTION_NAME, DataSize.of(30, KILOBYTE)); + assertThat(testOutput.getPositionCount()).isEqualTo(actualPositionsWritten * 2 + blockSet.size()); + } + + private static BlockSet createBlockSet(Type type, int expectedSize) + { + return new BlockSet( + type, + BLOCK_TYPE_OPERATORS.getDistinctFromOperator(type), + BLOCK_TYPE_OPERATORS.getHashCodeOperator(type), + expectedSize); + } +} diff --git a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java index 445acd7fe591..e86fdc6610da 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java @@ -39,7 +39,7 @@ import java.util.Collections; import static io.trino.block.BlockSerdeUtil.writeBlock; -import static io.trino.operator.aggregation.TypedSet.MAX_FUNCTION_MEMORY; +import static io.trino.operator.scalar.BlockSet.MAX_FUNCTION_MEMORY; import static io.trino.spi.StandardErrorCode.AMBIGUOUS_FUNCTION_CALL; import static io.trino.spi.StandardErrorCode.EXCEEDED_FUNCTION_MEMORY_LIMIT; import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND;