diff --git a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommon.java b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommon.java index afaf712a2d59..0c1b91dd3f0a 100644 --- a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommon.java +++ b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommon.java @@ -47,8 +47,8 @@ public class BinaryTupleCommon { public static final int PREFIX_FLAG = 1 << 3; /** - * Flag, which indicates how to interpret situations when Binary Tuple Prefix columns are equal to - * first N columns of a Binary Tuple (where N is the length of the prefix). + * Flag, which indicates how to interpret situations when Binary Tuple Prefix columns are equal to first N columns of a Binary Tuple + * (where N is the length of the prefix). * *

This flag is used by some index implementations for internal optimizations. */ @@ -133,4 +133,26 @@ public static int nullOffset(int index) { public static byte nullMask(int index) { return (byte) (1 << (index % 8)); } + + /** + * Calculates the size of entry in variable-length offset table. + * + * @param size Variable-length area size. + * @return Size in bytes. + */ + public static int valueSizeToEntrySize(long size) { + if (size <= 0xff) { + return 1; + } + + if (size <= 0xffff) { + return 2; + } + + if (size <= Integer.MAX_VALUE) { + return 4; + } + + throw new IgniteInternalException("Too big binary tuple size"); + } } diff --git a/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommonTest.java b/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommonTest.java new file mode 100644 index 000000000000..d618e44995d8 --- /dev/null +++ b/modules/binary-tuple/src/test/java/org/apache/ignite/internal/binarytuple/BinaryTupleCommonTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.binarytuple; + +import static org.apache.ignite.internal.binarytuple.BinaryTupleCommon.valueSizeToEntrySize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.ignite.lang.IgniteInternalException; +import org.junit.jupiter.api.Test; + +/** + * For {@link BinaryTupleCommon} testing. + */ +public class BinaryTupleCommonTest { + @Test + void testTableOffsetEntrySize() { + assertEquals(1, valueSizeToEntrySize(10)); + assertEquals(1, valueSizeToEntrySize(Byte.MAX_VALUE)); + assertEquals(1, valueSizeToEntrySize(0xff)); + + assertEquals(2, valueSizeToEntrySize(0x1ff)); + assertEquals(2, valueSizeToEntrySize(Short.MAX_VALUE)); + assertEquals(2, valueSizeToEntrySize(0xffff)); + + assertEquals(4, valueSizeToEntrySize(0x1ffff)); + assertEquals(4, valueSizeToEntrySize(Integer.MAX_VALUE)); + + assertThrows(IgniteInternalException.class, () -> valueSizeToEntrySize(0xffffffffL)); + } +} diff --git a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusInnerIo.java b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusInnerIo.java index 330e36a3d404..0a1cbe5519b2 100644 --- a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusInnerIo.java +++ b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusInnerIo.java @@ -41,6 +41,9 @@ public abstract class BplusInnerIo extends BplusIo { /** Offset of the link. */ private static final int SHIFT_LINK = SHIFT_LEFT + PARTITIONLESS_LINK_SIZE_BYTES; + /** Number of bytes a child link takes in storage. */ + public static final int CHILD_LINK_SIZE = PARTITIONLESS_LINK_SIZE_BYTES; + /** Offset of the right page ID of the item. */ private final int shiftRight = SHIFT_LINK + itemSize; diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java index 6308687c1089..fff7ad5e5b4b 100644 --- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java +++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java @@ -101,7 +101,7 @@ public static NativeType numberOf(int precision) { /** * Creates a STRING type with maximal length is len. * - * @param len Maximum length of the string. + * @param len Maximum length of the string, {@link Integer#MAX_VALUE} if not defined. * @return Native type. */ public static NativeType stringOf(int len) { @@ -111,7 +111,7 @@ public static NativeType stringOf(int len) { /** * Creates a BYTES type with maximal length is len. * - * @param len Maximum length of the byte array. + * @param len Maximum length of the byte array, {@link Integer#MAX_VALUE} if not defined. * @return Native type. */ public static NativeType blobOf(int len) { diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/VarlenNativeType.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/VarlenNativeType.java index 89613edb850e..692083528052 100644 --- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/VarlenNativeType.java +++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/VarlenNativeType.java @@ -23,14 +23,14 @@ * Variable-length native type. */ public class VarlenNativeType extends NativeType { - /** Length of the type. */ + /** Length of the type, {@link Integer#MAX_VALUE} if not defined. */ private final int len; /** * Constructor. * * @param typeSpec Type spec. - * @param len Type length. + * @param len Type length. */ protected VarlenNativeType(NativeTypeSpec typeSpec, int len) { super(typeSpec); @@ -38,20 +38,18 @@ protected VarlenNativeType(NativeTypeSpec typeSpec, int len) { this.len = len; } - /** {@inheritDoc} */ @Override public boolean mismatch(NativeType type) { return super.mismatch(type) || len < ((VarlenNativeType) type).len; } /** - * Get length of the type. + * Get length of the type, {@link Integer#MAX_VALUE} if not defined. */ public int length() { return len; } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(VarlenNativeType.class.getSimpleName(), "name", spec(), "len", len); diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java index f86b3d678e9d..95e2a419e725 100644 --- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java +++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/BinaryTupleComparator.java @@ -29,7 +29,7 @@ import org.apache.ignite.internal.schema.BinaryTupleSchema; import org.apache.ignite.internal.schema.NativeTypeSpec; import org.apache.ignite.internal.schema.row.InternalTuple; -import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.ColumnDescriptor; +import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.SortedIndexColumnDescriptor; /** * Comparator implementation for comparing {@link BinaryTuple}s on a per-column basis. @@ -63,10 +63,10 @@ public int compare(ByteBuffer buffer1, ByteBuffer buffer2) { int columnsToCompare = Math.min(tuple1.count(), tuple2.count()); - assert columnsToCompare <= descriptor.indexColumns().size(); + assert columnsToCompare <= descriptor.columns().size(); for (int i = 0; i < columnsToCompare; i++) { - ColumnDescriptor columnDescriptor = descriptor.indexColumns().get(i); + SortedIndexColumnDescriptor columnDescriptor = descriptor.columns().get(i); int compare = compareField(tuple1, tuple2, i); @@ -102,7 +102,7 @@ private int compareField(InternalTuple tuple1, InternalTuple tuple2, int index) return 1; } - ColumnDescriptor columnDescriptor = descriptor.indexColumns().get(index); + SortedIndexColumnDescriptor columnDescriptor = descriptor.columns().get(index); NativeTypeSpec typeSpec = columnDescriptor.type().spec(); diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java index e3740ccdfd3d..b546f4f51357 100644 --- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java +++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java @@ -38,40 +38,34 @@ * * @see HashIndexStorage */ -public class HashIndexDescriptor { +public class HashIndexDescriptor implements IndexDescriptor { /** * Descriptor of a Hash Index column. */ - public static class ColumnDescriptor { + public static class HashIndexColumnDescriptor implements ColumnDescriptor { private final String name; private final NativeType type; private final boolean nullable; - ColumnDescriptor(ColumnView tableColumnView) { + HashIndexColumnDescriptor(ColumnView tableColumnView) { this.name = tableColumnView.name(); this.type = ConfigurationToSchemaDescriptorConverter.convert(tableColumnView.type()); this.nullable = tableColumnView.nullable(); } - /** - * Returns the name of an index column. - */ + @Override public String name() { return name; } - /** - * Returns a column type. - */ + @Override public NativeType type() { return type; } - /** - * Returns {@code true} if this column can contain null values or {@code false} otherwise. - */ + @Override public boolean nullable() { return nullable; } @@ -84,7 +78,7 @@ public String toString() { private final UUID id; - private final List columns; + private final List columns; /** * Creates an Index Descriptor from a given Table Configuration. @@ -122,22 +116,18 @@ public HashIndexDescriptor(UUID indexId, TablesView tablesConfig) { assert columnView != null : "Incorrect index column configuration. " + columnName + " column does not exist"; - return new ColumnDescriptor(columnView); + return new HashIndexColumnDescriptor(columnView); }) .collect(toUnmodifiableList()); } - /** - * Returns the ID of this Index. - */ + @Override public UUID id() { return id; } - /** - * Returns the Column Descriptors that comprise a row of this index. - */ - public List indexColumns() { + @Override + public List columns() { return columns; } } diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexDescriptor.java new file mode 100644 index 000000000000..5dd2fb94657f --- /dev/null +++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexDescriptor.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.index; + +import java.util.List; +import java.util.UUID; +import org.apache.ignite.internal.schema.NativeType; + +/** + * Index descriptor. + */ +public interface IndexDescriptor { + /** + * Index column descriptor. + */ + interface ColumnDescriptor { + /** + * Returns the name of an index column. + */ + String name(); + + /** + * Returns a column type. + */ + NativeType type(); + + /** + * Returns {@code true} if this column can contain null values or {@code false} otherwise. + */ + boolean nullable(); + } + + /** + * Returns the index ID. + */ + UUID id(); + + /** + * Returns index column descriptions. + */ + List columns(); +} diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexDescriptor.java index a278f114de22..da5faa8fadaf 100644 --- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexDescriptor.java +++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexDescriptor.java @@ -41,11 +41,11 @@ * * @see SortedIndexStorage */ -public class SortedIndexDescriptor { +public class SortedIndexDescriptor implements IndexDescriptor { /** * Descriptor of a Sorted Index column (column name and column sort order). */ - public static class ColumnDescriptor { + public static class SortedIndexColumnDescriptor implements ColumnDescriptor { private final String name; private final NativeType type; @@ -62,7 +62,7 @@ public static class ColumnDescriptor { * @param nullable Flag indicating that the column may contain {@code null}s. * @param asc Sort order of the column. */ - public ColumnDescriptor(String name, NativeType type, boolean nullable, boolean asc) { + public SortedIndexColumnDescriptor(String name, NativeType type, boolean nullable, boolean asc) { this.name = name; this.type = type; this.nullable = nullable; @@ -75,30 +75,24 @@ public ColumnDescriptor(String name, NativeType type, boolean nullable, boolean * @param tableColumnView Table column configuration. * @param indexColumnView Index column configuration. */ - public ColumnDescriptor(ColumnView tableColumnView, IndexColumnView indexColumnView) { + public SortedIndexColumnDescriptor(ColumnView tableColumnView, IndexColumnView indexColumnView) { this.name = tableColumnView.name(); this.type = ConfigurationToSchemaDescriptorConverter.convert(tableColumnView.type()); this.nullable = tableColumnView.nullable(); this.asc = indexColumnView.asc(); } - /** - * Returns the name of an index column. - */ + @Override public String name() { return name; } - /** - * Returns a column descriptor. - */ + @Override public NativeType type() { return type; } - /** - * Returns {@code true} if this column can contain null values or {@code false} otherwise. - */ + @Override public boolean nullable() { return nullable; } @@ -118,7 +112,7 @@ public String toString() { private final UUID id; - private final List columns; + private final List columns; private final BinaryTupleSchema binaryTupleSchema; @@ -138,13 +132,13 @@ public SortedIndexDescriptor(UUID indexId, TablesView tablesConfig) { * @param indexId Index ID. * @param columnDescriptors Column descriptors. */ - public SortedIndexDescriptor(UUID indexId, List columnDescriptors) { + public SortedIndexDescriptor(UUID indexId, List columnDescriptors) { this.id = indexId; this.columns = List.copyOf(columnDescriptors); this.binaryTupleSchema = createSchema(columns); } - private static List extractIndexColumnsConfiguration(UUID indexId, TablesView tablesConfig) { + private static List extractIndexColumnsConfiguration(UUID indexId, TablesView tablesConfig) { TableIndexView indexConfig = ConfigurationUtil.getByInternalId(tablesConfig.indexes(), indexId); if (indexConfig == null) { @@ -174,12 +168,12 @@ private static List extractIndexColumnsConfiguration(UUID inde IndexColumnView indexColumnView = indexColumns.get(columnName); - return new ColumnDescriptor(columnView, indexColumnView); + return new SortedIndexColumnDescriptor(columnView, indexColumnView); }) .collect(toUnmodifiableList()); } - private static BinaryTupleSchema createSchema(List columns) { + private static BinaryTupleSchema createSchema(List columns) { Element[] elements = columns.stream() .map(columnDescriptor -> new Element(columnDescriptor.type(), columnDescriptor.nullable())) .toArray(Element[]::new); @@ -187,17 +181,13 @@ private static BinaryTupleSchema createSchema(List columns) { return BinaryTupleSchema.create(elements); } - /** - * Returns this index' ID. - */ + @Override public UUID id() { return id; } - /** - * Returns the Column Descriptors that comprise a row of this index. - */ - public List indexColumns() { + @Override + public List columns() { return columns; } diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java index 3c929ba1d882..8e614273faf4 100644 --- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java +++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/BinaryTupleComparatorTest.java @@ -37,7 +37,7 @@ import org.apache.ignite.internal.binarytuple.BinaryTuplePrefixBuilder; import org.apache.ignite.internal.schema.NativeType; import org.apache.ignite.internal.schema.NativeTypes; -import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.ColumnDescriptor; +import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.SortedIndexColumnDescriptor; import org.apache.ignite.lang.IgniteBiTuple; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -51,7 +51,7 @@ public class BinaryTupleComparatorTest { @ParameterizedTest @MethodSource("allTypes") public void testCompareSingleColumnTuples(NativeType type) { - var columnDescriptor = new ColumnDescriptor("column", type, false, true); + var columnDescriptor = new SortedIndexColumnDescriptor("column", type, false, true); var descriptor = new SortedIndexDescriptor(UUID.randomUUID(), List.of(columnDescriptor)); @@ -300,9 +300,9 @@ private static IgniteBiTuple createTestValues(NativeType @Test public void testCompareMultipleColumnTuples() { - List columnDescriptors = List.of( - new ColumnDescriptor("column", NativeTypes.INT32, false, true), - new ColumnDescriptor("column", NativeTypes.STRING, false, false) + List columnDescriptors = List.of( + new SortedIndexColumnDescriptor("column", NativeTypes.INT32, false, true), + new SortedIndexColumnDescriptor("column", NativeTypes.STRING, false, false) ); var descriptor = new SortedIndexDescriptor(UUID.randomUUID(), columnDescriptors); @@ -334,9 +334,9 @@ public void testCompareMultipleColumnTuples() { @Test public void testCompareMultipleColumnTuplesWithNulls() { - List columnDescriptors = List.of( - new ColumnDescriptor("column", NativeTypes.INT32, true, true), - new ColumnDescriptor("column", NativeTypes.STRING, true, false) + List columnDescriptors = List.of( + new SortedIndexColumnDescriptor("column", NativeTypes.INT32, true, true), + new SortedIndexColumnDescriptor("column", NativeTypes.STRING, true, false) ); var descriptor = new SortedIndexDescriptor(UUID.randomUUID(), columnDescriptors); @@ -377,9 +377,9 @@ public void testCompareMultipleColumnTuplesWithNulls() { @Test public void testCompareWithPrefix() { - List columnDescriptors = List.of( - new ColumnDescriptor("column", NativeTypes.INT32, false, true), - new ColumnDescriptor("column", NativeTypes.STRING, false, false) + List columnDescriptors = List.of( + new SortedIndexColumnDescriptor("column", NativeTypes.INT32, false, true), + new SortedIndexColumnDescriptor("column", NativeTypes.STRING, false, false) ); var descriptor = new SortedIndexDescriptor(UUID.randomUUID(), columnDescriptors); @@ -420,9 +420,9 @@ public void testCompareWithPrefix() { @Test public void testCompareWithPrefixWithNulls() { - List columnDescriptors = List.of( - new ColumnDescriptor("column", NativeTypes.INT32, true, true), - new ColumnDescriptor("column", NativeTypes.STRING, false, false) + List columnDescriptors = List.of( + new SortedIndexColumnDescriptor("column", NativeTypes.INT32, true, true), + new SortedIndexColumnDescriptor("column", NativeTypes.STRING, false, false) ); var descriptor = new SortedIndexDescriptor(UUID.randomUUID(), columnDescriptors); diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java index 7ba2a0231b9e..6f241dd086d1 100644 --- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java +++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java @@ -69,7 +69,7 @@ import org.apache.ignite.internal.storage.MvPartitionStorage; import org.apache.ignite.internal.storage.RowId; import org.apache.ignite.internal.storage.engine.MvTableStorage; -import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.ColumnDescriptor; +import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.SortedIndexColumnDescriptor; import org.apache.ignite.internal.storage.index.impl.BinaryTupleRowSerializer; import org.apache.ignite.internal.storage.index.impl.TestIndexRow; import org.apache.ignite.internal.testframework.VariableSource; @@ -212,8 +212,8 @@ private SortedIndexStorage createIndexStorage(ColumnarIndexDefinition indexDefin void testRowSerialization() { SortedIndexStorage indexStorage = createIndexStorage(ALL_TYPES_COLUMN_DEFINITIONS); - Object[] columns = indexStorage.indexDescriptor().indexColumns().stream() - .map(ColumnDescriptor::type) + Object[] columns = indexStorage.indexDescriptor().columns().stream() + .map(SortedIndexColumnDescriptor::type) .map(type -> SchemaTestUtils.generateRandomValue(random, type)) .toArray(); diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java index 347bd8cc4e6e..9b029d00180c 100644 --- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java +++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java @@ -58,7 +58,7 @@ private static class ColumnDescriptor { * Creates a new instance for a Sorted Index. */ public BinaryTupleRowSerializer(SortedIndexDescriptor descriptor) { - this(descriptor.indexColumns().stream() + this(descriptor.columns().stream() .map(colDesc -> new ColumnDescriptor(colDesc.type(), colDesc.nullable())) .collect(toUnmodifiableList())); } @@ -67,7 +67,7 @@ public BinaryTupleRowSerializer(SortedIndexDescriptor descriptor) { * Creates a new instance for a Hash Index. */ public BinaryTupleRowSerializer(HashIndexDescriptor descriptor) { - this(descriptor.indexColumns().stream() + this(descriptor.columns().stream() .map(colDesc -> new ColumnDescriptor(colDesc.type(), colDesc.nullable())) .collect(toUnmodifiableList())); } diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java index b2f072d00918..5ddd6f03e952 100644 --- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java +++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java @@ -32,7 +32,7 @@ import org.apache.ignite.internal.schema.SchemaTestUtils; import org.apache.ignite.internal.storage.RowId; import org.apache.ignite.internal.storage.index.IndexRow; -import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.ColumnDescriptor; +import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.SortedIndexColumnDescriptor; import org.apache.ignite.internal.storage.index.SortedIndexStorage; /** @@ -64,8 +64,8 @@ public TestIndexRow(SortedIndexStorage storage, BinaryTupleRowSerializer seriali public static TestIndexRow randomRow(SortedIndexStorage indexStorage) { var random = new Random(); - Object[] columns = indexStorage.indexDescriptor().indexColumns().stream() - .map(ColumnDescriptor::type) + Object[] columns = indexStorage.indexDescriptor().columns().stream() + .map(SortedIndexColumnDescriptor::type) .map(type -> generateRandomValue(random, type)) .toArray(); @@ -109,7 +109,7 @@ public int compareTo(TestIndexRow o) { int compare = comparator.compare(columns[i], o.columns[i]); if (compare != 0) { - boolean asc = indexStorage.indexDescriptor().indexColumns().get(i).asc(); + boolean asc = indexStorage.indexDescriptor().columns().get(i).asc(); return asc ? compare : -compare; } diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/InlineUtils.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/InlineUtils.java new file mode 100644 index 000000000000..4f69d569f7b0 --- /dev/null +++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/InlineUtils.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.pagememory.index; + +import static org.apache.ignite.internal.binarytuple.BinaryTupleCommon.valueSizeToEntrySize; +import static org.apache.ignite.internal.pagememory.tree.io.BplusInnerIo.CHILD_LINK_SIZE; +import static org.apache.ignite.internal.util.Constants.KiB; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import org.apache.ignite.internal.binarytuple.BinaryTupleCommon; +import org.apache.ignite.internal.pagememory.freelist.FreeList; +import org.apache.ignite.internal.pagememory.tree.BplusTree; +import org.apache.ignite.internal.pagememory.tree.io.BplusInnerIo; +import org.apache.ignite.internal.pagememory.tree.io.BplusLeafIo; +import org.apache.ignite.internal.schema.BinaryTuple; +import org.apache.ignite.internal.schema.NativeType; +import org.apache.ignite.internal.schema.NativeTypeSpec; +import org.apache.ignite.internal.schema.VarlenNativeType; +import org.apache.ignite.internal.storage.index.IndexDescriptor; +import org.apache.ignite.internal.storage.index.IndexDescriptor.ColumnDescriptor; + +/** + * Helper class for index inlining. + * + *

Index inlining is an optimization that allows index rows (or prefix) to be compared in a {@link BplusTree} without loading them from a + * {@link FreeList}. + */ +public class InlineUtils { + /** Maximum inline size for a {@link BinaryTuple}, in bytes. */ + public static final int MAX_BINARY_TUPLE_INLINE_SIZE = 2 * KiB; + + /** Heuristic maximum inline size of a variable length column in bytes. */ + static final int MAX_VARLEN_INLINE_SIZE = 64; + + /** Heuristic maximum size of an entry in the {@link BinaryTuple} offset table, in bytes. */ + static final int MAX_BINARY_TUPLE_OFFSET_TABLE_ENTRY_SIZE = 2; + + /** + * Minimum number of items in a {@link BplusInnerIo} so that the search in the B+tree does not lose much in performance due to its rapid + * growth. + */ + static final int MIN_INNER_PAGE_ITEM_COUNT = 2; + + /** Heuristic maximum inline size for {@link BigDecimal} and {@link BigInteger} column in bytes. */ + static final int BIG_NUMBER_INLINE_SIZE = 4; + + /** + * Calculates inline size for column. + * + * @param nativeType Column type. + * @return Inline size in bytes. + */ + static int inlineSize(NativeType nativeType) { + NativeTypeSpec spec = nativeType.spec(); + + if (spec.fixedLength()) { + return nativeType.sizeInBytes(); + } + + // Variable length columns. + + switch (spec) { + case STRING: + case BYTES: + return Math.min(MAX_VARLEN_INLINE_SIZE, ((VarlenNativeType) nativeType).length()); + + case DECIMAL: + case NUMBER: + return BIG_NUMBER_INLINE_SIZE; + + default: + throw new IllegalArgumentException("Unknown type " + spec); + } + } + + /** + * Calculates inline size for {@link BinaryTuple}, given its format. + * + * @param indexDescriptor Index descriptor. + * @return Inline size in bytes, no more than {@link #MAX_BINARY_TUPLE_INLINE_SIZE}. + */ + static int binaryTupleInlineSize(IndexDescriptor indexDescriptor) { + List columns = indexDescriptor.columns(); + + assert !columns.isEmpty(); + + boolean hasNullColumns = columns.stream().anyMatch(ColumnDescriptor::nullable); + + // Let's calculate the inline size for all columns. + int columnsInlineSize = columns.stream().map(ColumnDescriptor::type).mapToInt(InlineUtils::inlineSize).sum(); + + int inlineSize = BinaryTupleCommon.HEADER_SIZE + + (hasNullColumns ? BinaryTupleCommon.nullMapSize(columns.size()) : 0) + + columns.size() * Math.min(MAX_BINARY_TUPLE_OFFSET_TABLE_ENTRY_SIZE, valueSizeToEntrySize(columnsInlineSize)) + + columnsInlineSize; + + return Math.min(inlineSize, MAX_BINARY_TUPLE_INLINE_SIZE); + } + + /** + * Calculates the inline size of {@link BinaryTuple} that will be stored in the {@link BplusInnerIo} and {@link BplusLeafIo} item. + * + * @param pageSize Page size in bytes. + * @param itemHeaderSize Size of the item header that is stored in the {@link BplusInnerIo} and {@link BplusLeafIo}, in bytes. + * @param indexDescriptor Index descriptor. + * @return Inline size in bytes, no more than {@link #MAX_BINARY_TUPLE_INLINE_SIZE}. + */ + public static int binaryTupleInlineSize(int pageSize, int itemHeaderSize, IndexDescriptor indexDescriptor) { + int maxInnerNodeItemSize = ((innerNodePayloadSize(pageSize) - CHILD_LINK_SIZE) / MIN_INNER_PAGE_ITEM_COUNT) - CHILD_LINK_SIZE; + + int binaryTupleInlineSize = Math.min(maxInnerNodeItemSize - itemHeaderSize, binaryTupleInlineSize(indexDescriptor)); + + if (binaryTupleInlineSize >= MAX_BINARY_TUPLE_INLINE_SIZE) { + return MAX_BINARY_TUPLE_INLINE_SIZE; + } + + // If we have variable length columns and there is enough unused space in the innerNodes and leafNodes, then we can try increasing + // the inline size for the BinaryTuple. Example: pageSize = 1024, binaryTupleInlineSize = 124, BplusLeafIo.HEADER_SIZE = 56. + // In this case, the innerNode will fit 7 (with child links) items and 7 items in the leafNode, while 52 bytes will remain free for + // the innerNode items, and 100 bytes for the leafNode items, which we could use. For a innerNode, we can add (52 / 7) = 7 bytes + // for each item (with link), and for a leafNode, (100 / 7) = 14 bytes for each item, so we can safely use the 7 extra bytes for the + // innerNode and leafNode per item. + + if (indexDescriptor.columns().stream().anyMatch(c -> !c.type().spec().fixedLength())) { + int itemSize = binaryTupleInlineSize + itemHeaderSize; + + int innerNodeItemSize = + optimizeItemSize(innerNodePayloadSize(pageSize) - CHILD_LINK_SIZE, itemSize + CHILD_LINK_SIZE) - CHILD_LINK_SIZE; + + int leafNodeItemSize = optimizeItemSize(leafNodePayloadSize(pageSize), itemSize); + + int optimizedItemSize = Math.min(innerNodeItemSize, leafNodeItemSize); + + assert leafNodePayloadSize(pageSize) / itemSize == leafNodePayloadSize(pageSize) / optimizedItemSize; + + binaryTupleInlineSize = optimizedItemSize - itemHeaderSize; + } + + return Math.min(binaryTupleInlineSize, MAX_BINARY_TUPLE_INLINE_SIZE); + } + + /** + * Returns number of bytes that can be used to store items and links to child nodes in an inner node. + * + * @param pageSize Page size in bytes. + */ + static int innerNodePayloadSize(int pageSize) { + return pageSize - BplusInnerIo.HEADER_SIZE; + } + + /** + * Returns number of bytes that can be used to store items in a leaf node. + * + * @param pageSize Page size in bytes. + */ + static int leafNodePayloadSize(int pageSize) { + return pageSize - BplusLeafIo.HEADER_SIZE; + } + + /** + * Optimizes the item size for a {@link BplusInnerIo} or {@link BplusLeafIo} if there is free space for each item. + * + *

We try to use the available memory on the page as much as possible. + * + * @param nodePayloadSize Payload size of a {@link BplusInnerIo} or {@link BplusLeafIo}, in bytes. + * @param itemSize Size of the item in {@link BplusInnerIo} or {@link BplusLeafIo}, in bytes. + * @return Size in bytes. + */ + static int optimizeItemSize(int nodePayloadSize, int itemSize) { + // Let's calculate how much space remains in the BplusInnerIo or the BplusLeafIo. + int remainingNodePayloadSize = nodePayloadSize % itemSize; + + int nodeItemCount = nodePayloadSize / itemSize; + + // Let's calculate how many additional bytes can be added for each item of the BplusInnerIo and the BplusLeafIo. + int additionalNodeItemBytes = remainingNodePayloadSize / nodeItemCount; + + return itemSize + additionalNodeItemBytes; + } +} diff --git a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/index/InlineUtilsTest.java b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/index/InlineUtilsTest.java new file mode 100644 index 000000000000..2b7cb6af1754 --- /dev/null +++ b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/index/InlineUtilsTest.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.pagememory.index; + +import static org.apache.ignite.internal.pagememory.tree.io.BplusInnerIo.CHILD_LINK_SIZE; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.BIG_NUMBER_INLINE_SIZE; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.MAX_BINARY_TUPLE_INLINE_SIZE; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.MAX_VARLEN_INLINE_SIZE; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.binaryTupleInlineSize; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.inlineSize; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.innerNodePayloadSize; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.leafNodePayloadSize; +import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.optimizeItemSize; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.EnumSet; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.ignite.internal.binarytuple.BinaryTupleCommon; +import org.apache.ignite.internal.pagememory.tree.io.BplusInnerIo; +import org.apache.ignite.internal.pagememory.tree.io.BplusLeafIo; +import org.apache.ignite.internal.schema.NativeType; +import org.apache.ignite.internal.schema.NativeTypeSpec; +import org.apache.ignite.internal.schema.NativeTypes; +import org.apache.ignite.internal.storage.index.IndexDescriptor; +import org.apache.ignite.internal.storage.index.IndexDescriptor.ColumnDescriptor; +import org.junit.jupiter.api.Test; + +/** + * For {@link InlineUtils} testing. + */ +public class InlineUtilsTest { + @Test + void testInlineSizeForNativeType() { + EnumSet nativeTypeSpecs = EnumSet.allOf(NativeTypeSpec.class); + + // Fixed length type checking. + + NativeType nativeType = NativeTypes.INT8; + + assertEquals(1, inlineSize(nativeType)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(2, inlineSize(nativeType = NativeTypes.INT16)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(4, inlineSize(nativeType = NativeTypes.INT32)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(8, inlineSize(nativeType = NativeTypes.INT64)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(4, inlineSize(nativeType = NativeTypes.FLOAT)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(8, inlineSize(nativeType = NativeTypes.DOUBLE)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(16, inlineSize(nativeType = NativeTypes.UUID)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(1, inlineSize(nativeType = NativeTypes.bitmaskOf(8))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(3, inlineSize(nativeType = NativeTypes.DATE)); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(4, inlineSize(nativeType = NativeTypes.time())); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(9, inlineSize(nativeType = NativeTypes.datetime())); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(12, inlineSize(nativeType = NativeTypes.timestamp())); + nativeTypeSpecs.remove(nativeType.spec()); + + // Variable length type checking. + + assertEquals(BIG_NUMBER_INLINE_SIZE, inlineSize(nativeType = NativeTypes.decimalOf(1, 1))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(BIG_NUMBER_INLINE_SIZE, inlineSize(nativeType = NativeTypes.decimalOf(100, 1))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(7, inlineSize(nativeType = NativeTypes.stringOf(7))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(MAX_VARLEN_INLINE_SIZE, inlineSize(nativeType = NativeTypes.stringOf(256))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(MAX_VARLEN_INLINE_SIZE, inlineSize(nativeType = NativeTypes.stringOf(Integer.MAX_VALUE))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(9, inlineSize(nativeType = NativeTypes.blobOf(9))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(MAX_VARLEN_INLINE_SIZE, inlineSize(nativeType = NativeTypes.blobOf(256))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(MAX_VARLEN_INLINE_SIZE, inlineSize(nativeType = NativeTypes.blobOf(Integer.MAX_VALUE))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(BIG_NUMBER_INLINE_SIZE, inlineSize(nativeType = NativeTypes.numberOf(1))); + nativeTypeSpecs.remove(nativeType.spec()); + + assertEquals(BIG_NUMBER_INLINE_SIZE, inlineSize(nativeType = NativeTypes.numberOf(100))); + nativeTypeSpecs.remove(nativeType.spec()); + + // Let's check that all types have been checked. + assertThat(nativeTypeSpecs, empty()); + } + + @Test + void testBinaryTupleInlineSize() { + IndexDescriptor indexDescriptor = testIndexDescriptor(testColumnDescriptor(NativeTypes.INT8, false)); + + assertEquals( + BinaryTupleCommon.HEADER_SIZE + 1 + NativeTypes.INT8.sizeInBytes(), // Without a nullMap card. + binaryTupleInlineSize(indexDescriptor) + ); + + indexDescriptor = testIndexDescriptor(testColumnDescriptor(NativeTypes.INT32, true)); + + assertEquals( + BinaryTupleCommon.HEADER_SIZE + 1 + 1 + NativeTypes.INT32.sizeInBytes(), // With a nullMap card. + binaryTupleInlineSize(indexDescriptor) + ); + + // Let's check the 2-byte entry size for the BinaryTuple offset table. + + indexDescriptor = testIndexDescriptor( + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false), + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false), + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false), + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false) + ); + + assertEquals( + BinaryTupleCommon.HEADER_SIZE + 4 * 2 + 4 * MAX_VARLEN_INLINE_SIZE, // Without a nullMap card. + binaryTupleInlineSize(indexDescriptor) + ); + + indexDescriptor = testIndexDescriptor( + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false), + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), true), + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false), + testColumnDescriptor(NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE), false) + ); + + assertEquals( + BinaryTupleCommon.HEADER_SIZE + 4 * 2 + 1 + 4 * MAX_VARLEN_INLINE_SIZE, // With a nullMap card. + binaryTupleInlineSize(indexDescriptor) + ); + + // Let's check that it does not exceed the MAX_BINARY_TUPLE_INLINE_SIZE. + + ColumnDescriptor[] columnDescriptors = IntStream.range(0, MAX_BINARY_TUPLE_INLINE_SIZE / MAX_VARLEN_INLINE_SIZE) + .mapToObj(i -> NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE)) + .map(nativeType -> testColumnDescriptor(nativeType, false)) + .toArray(ColumnDescriptor[]::new); + + assertEquals( + MAX_BINARY_TUPLE_INLINE_SIZE, // Without a nullMap card. + binaryTupleInlineSize(testIndexDescriptor(columnDescriptors)) + ); + + columnDescriptors = IntStream.range(0, MAX_BINARY_TUPLE_INLINE_SIZE / MAX_VARLEN_INLINE_SIZE) + .mapToObj(i -> NativeTypes.stringOf(MAX_VARLEN_INLINE_SIZE)) + .map(nativeType -> testColumnDescriptor(nativeType, true)) + .toArray(ColumnDescriptor[]::new); + + assertEquals( + MAX_BINARY_TUPLE_INLINE_SIZE, // With a nullMap card. + binaryTupleInlineSize(testIndexDescriptor(columnDescriptors)) + ); + } + + @Test + void testInnerNodePayloadSize() { + int pageSize = 1024; + + assertEquals(pageSize - BplusInnerIo.HEADER_SIZE, innerNodePayloadSize(pageSize)); + + pageSize = 128; + + assertEquals(pageSize - BplusInnerIo.HEADER_SIZE, innerNodePayloadSize(pageSize)); + } + + @Test + void testLeafNodePayloadSize() { + int pageSize = 1024; + + assertEquals(pageSize - BplusLeafIo.HEADER_SIZE, leafNodePayloadSize(pageSize)); + + pageSize = 128; + + assertEquals(pageSize - BplusLeafIo.HEADER_SIZE, leafNodePayloadSize(pageSize)); + } + + @Test + void testBinaryTupleInlineSizeForBplusTree() { + int pageSize = 1024; + int itemHeaderSize = 6; + + // Let's check without variable length columns. + + IndexDescriptor indexDescriptor = testIndexDescriptor( + testColumnDescriptor(NativeTypes.INT64, false), + testColumnDescriptor(NativeTypes.UUID, false) + ); + + assertEquals( + BinaryTupleCommon.HEADER_SIZE + 2 + NativeTypes.INT64.sizeInBytes() + NativeTypes.UUID.sizeInBytes(), + binaryTupleInlineSize(pageSize, itemHeaderSize, indexDescriptor) + ); + + indexDescriptor = testIndexDescriptor( + testColumnDescriptor(NativeTypes.INT64, false), + testColumnDescriptor(NativeTypes.UUID, false), + testColumnDescriptor(NativeTypes.UUID, false), + testColumnDescriptor(NativeTypes.UUID, false), + testColumnDescriptor(NativeTypes.UUID, true) + ); + + assertEquals( + (((innerNodePayloadSize(128) - CHILD_LINK_SIZE) / 2) - CHILD_LINK_SIZE) - itemHeaderSize, + binaryTupleInlineSize(128, itemHeaderSize, indexDescriptor) + ); + + // Let's check without variable length columns. + + indexDescriptor = testIndexDescriptor( + testColumnDescriptor(NativeTypes.stringOf(32), true) + ); + + assertEquals( + (((innerNodePayloadSize(128) - CHILD_LINK_SIZE) / 2) - CHILD_LINK_SIZE) - itemHeaderSize, + binaryTupleInlineSize(128, itemHeaderSize, indexDescriptor) + ); + + indexDescriptor = testIndexDescriptor( + testColumnDescriptor(NativeTypes.INT64, false), + testColumnDescriptor(NativeTypes.UUID, false), + testColumnDescriptor(NativeTypes.UUID, false), + testColumnDescriptor(NativeTypes.UUID, false), + testColumnDescriptor(NativeTypes.stringOf(32), true) + ); + + assertEquals( + BinaryTupleCommon.HEADER_SIZE + 1 + 5 + NativeTypes.INT64.sizeInBytes() + 3 * NativeTypes.UUID.sizeInBytes() + 32 + 6, + binaryTupleInlineSize(pageSize, itemHeaderSize, indexDescriptor) + ); + } + + @Test + void testOptimizeItemSize() { + assertEquals(100, optimizeItemSize(1000, 100)); + + assertEquals(333, optimizeItemSize(1000, 330)); + } + + private static IndexDescriptor testIndexDescriptor(ColumnDescriptor... columnDescriptors) { + IndexDescriptor indexDescriptor = mock(IndexDescriptor.class); + + when(indexDescriptor.columns()).then(answer -> List.of(columnDescriptors)); + + return indexDescriptor; + } + + private static ColumnDescriptor testColumnDescriptor(NativeType nativeType, boolean nullable) { + ColumnDescriptor columnDescriptor = mock(ColumnDescriptor.class); + + when(columnDescriptor.type()).thenReturn(nativeType); + when(columnDescriptor.nullable()).thenReturn(nullable); + + return columnDescriptor; + } +}