From dea1c90c3463a642334e7b47c00a828e1a4dbaca Mon Sep 17 00:00:00 2001 From: sepgh Date: Sun, 7 Jul 2024 19:59:27 +0330 Subject: [PATCH] feat: implement SingleFileIndexStorageManager --- .../SingleFileIndexStorageManager.java | 49 ++++ ...exManagerSingleStorageManagerTestCase.java | 230 ++++++++++++++++++ ...SingleFileIndexStorageManagerTestCase.java | 221 +++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 src/main/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManager.java create mode 100644 src/test/java/com/github/sepgh/testudo/index/tree/storing/MultiTableBPlusTreeIndexManagerSingleStorageManagerTestCase.java create mode 100644 src/test/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManagerTestCase.java diff --git a/src/main/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManager.java b/src/main/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManager.java new file mode 100644 index 0000000..d87d367 --- /dev/null +++ b/src/main/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManager.java @@ -0,0 +1,49 @@ +package com.github.sepgh.testudo.storage; + +import com.github.sepgh.testudo.EngineConfig; +import com.github.sepgh.testudo.index.Pointer; +import com.github.sepgh.testudo.storage.header.HeaderManager; +import com.github.sepgh.testudo.storage.pool.FileHandlerPool; +import com.github.sepgh.testudo.storage.pool.ManagedFileHandler; +import com.github.sepgh.testudo.utils.FileUtils; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.file.Path; +import java.util.concurrent.ExecutionException; + +public class SingleFileIndexStorageManager extends BaseFileIndexStorageManager { + + public SingleFileIndexStorageManager(Path path, @Nullable String customName, HeaderManager headerManager, EngineConfig engineConfig, FileHandlerPool fileHandlerPool) throws IOException, ExecutionException, InterruptedException { + super(path, customName, headerManager, engineConfig, fileHandlerPool); + } + + public SingleFileIndexStorageManager(Path path, @Nullable String customName, HeaderManager headerManager, EngineConfig engineConfig, FileHandlerPool fileHandlerPool, int binarySpace) throws IOException, ExecutionException, InterruptedException { + super(path, customName, headerManager, engineConfig, fileHandlerPool, binarySpace); + } + + public SingleFileIndexStorageManager(Path path, HeaderManager headerManager, EngineConfig engineConfig, FileHandlerPool fileHandlerPool, int binarySpaceMax) throws IOException, ExecutionException, InterruptedException { + super(path, headerManager, engineConfig, fileHandlerPool, binarySpaceMax); + } + + public SingleFileIndexStorageManager(Path path, HeaderManager headerManager, EngineConfig engineConfig, int binarySpaceMax) throws IOException, ExecutionException, InterruptedException { + super(path, headerManager, engineConfig, binarySpaceMax); + } + + protected Path getIndexFilePath(int table, int chunk) { + if (customName == null) + return Path.of(path.toString(), String.format("%s.bin", INDEX_FILE_NAME)); + return Path.of(path.toString(), String.format("%s.%s.bin", INDEX_FILE_NAME, customName)); + } + + protected Pointer getAllocatedSpaceForNewNode(int tableId, int chunk) throws IOException, ExecutionException, InterruptedException { + ManagedFileHandler managedFileHandler = this.getManagedFileHandler(tableId, 0); + AsynchronousFileChannel asynchronousFileChannel = managedFileHandler.getAsynchronousFileChannel(); + + Long position = FileUtils.allocate(asynchronousFileChannel, this.getIndexGrowthAllocationSize()).get(); + managedFileHandler.close(); + return new Pointer(Pointer.TYPE_NODE, position, chunk); + } + +} diff --git a/src/test/java/com/github/sepgh/testudo/index/tree/storing/MultiTableBPlusTreeIndexManagerSingleStorageManagerTestCase.java b/src/test/java/com/github/sepgh/testudo/index/tree/storing/MultiTableBPlusTreeIndexManagerSingleStorageManagerTestCase.java new file mode 100644 index 0000000..27536f1 --- /dev/null +++ b/src/test/java/com/github/sepgh/testudo/index/tree/storing/MultiTableBPlusTreeIndexManagerSingleStorageManagerTestCase.java @@ -0,0 +1,230 @@ +package com.github.sepgh.testudo.index.tree.storing; + +import com.github.sepgh.testudo.EngineConfig; +import com.github.sepgh.testudo.exception.IndexExistsException; +import com.github.sepgh.testudo.exception.InternalOperationException; +import com.github.sepgh.testudo.helper.IndexFileDescriptor; +import com.github.sepgh.testudo.index.IndexManager; +import com.github.sepgh.testudo.index.Pointer; +import com.github.sepgh.testudo.index.tree.node.AbstractLeafTreeNode; +import com.github.sepgh.testudo.index.tree.node.AbstractTreeNode; +import com.github.sepgh.testudo.index.tree.node.cluster.ClusterBPlusTreeIndexManager; +import com.github.sepgh.testudo.index.tree.node.data.ImmutableBinaryObjectWrapper; +import com.github.sepgh.testudo.index.tree.node.data.LongImmutableBinaryObjectWrapper; +import com.github.sepgh.testudo.index.tree.node.data.PointerImmutableBinaryObjectWrapper; +import com.github.sepgh.testudo.storage.BTreeSizeCalculator; +import com.github.sepgh.testudo.storage.InMemoryHeaderManager; +import com.github.sepgh.testudo.storage.SingleFileIndexStorageManager; +import com.github.sepgh.testudo.storage.header.Header; +import com.github.sepgh.testudo.storage.header.HeaderManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static com.github.sepgh.testudo.storage.SingleFileIndexStorageManager.INDEX_FILE_NAME; + +public class MultiTableBPlusTreeIndexManagerSingleStorageManagerTestCase { + private Path dbPath; + private EngineConfig engineConfig; + private Header header; + private final int degree = 4; + + @BeforeEach + public void setUp() throws IOException { + dbPath = Files.createTempDirectory("TEST_MultiTableBPlusTreeIndexManagerSingleStorageManagerTestCase"); + engineConfig = EngineConfig.builder() + .bTreeDegree(degree) + .bTreeGrowthNodeAllocationCount(10) + .build(); + engineConfig.setBTreeMaxFileSize(2 * 15L * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)); + + byte[] writingBytes = new byte[2 * 13 * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)]; + Path indexPath = Path.of(dbPath.toString(), String.format("%s.bin", INDEX_FILE_NAME)); + Files.write(indexPath, writingBytes, StandardOpenOption.WRITE, StandardOpenOption.CREATE); + + header = Header.builder() + .database("sample") + .tables( + Arrays.asList( + Header.Table.builder() + .id(1) + .name("test") + .chunks( + Collections.singletonList( + Header.IndexChunk.builder() + .chunk(0) + .offset(0) + .build() + ) + ) + .root( + Header.IndexChunk.builder() + .chunk(0) + .offset(0) + .build() + ) + .initialized(true) + .build(), + Header.Table.builder() + .id(2) + .name("test2") + .chunks( + Collections.singletonList( + Header.IndexChunk.builder() + .chunk(0) + .offset(12L * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)) + .build() + ) + ) + .root( + Header.IndexChunk.builder() + .chunk(0) + .offset(12L * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)) + .build() + ) + .initialized(true) + .build() + ) + ) + .build(); + + Assertions.assertTrue(header.getTableOfId(1).isPresent()); + Assertions.assertTrue(header.getTableOfId(1).get().getIndexChunk(0).isPresent()); + Assertions.assertTrue(header.getTableOfId(2).isPresent()); + Assertions.assertTrue(header.getTableOfId(2).get().getIndexChunk(0).isPresent()); + } + + @AfterEach + public void destroy() throws IOException { + Path indexPath0 = Path.of(dbPath.toString(), String.format("%s.bin", INDEX_FILE_NAME)); + Files.delete(indexPath0); + } + + + /** + * + * The B+Tree in this test will include numbers from [1-12] added respectively + * The shape of the tree will be like below, and the test verifies that + * The test validates the tree for 2 tables in same database + * 007 + * ├── . + * │ ├── 001 + * │ └── 002 + * ├── 003 + * │ ├── 003 + * │ └── 004 + * ├── 005 + * │ ├── 005 + * │ └── 006 + * ├── . + * │ ├── 007 + * │ └── 008 + * ├── 009 + * │ ├── 009 + * │ └── 010 + * └── 0011 + * ├── 011 + * └── 012 + */ + @Test + public void testMultiSplitAddIndex() throws IOException, ExecutionException, InterruptedException, ImmutableBinaryObjectWrapper.InvalidBinaryObjectWrapperValue, IndexExistsException, InternalOperationException { + + List testIdentifiers = Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L); + Pointer samplePointer = new Pointer(Pointer.TYPE_DATA, 100, 0); + + for (int tableId = 1; tableId <= 2; tableId++){ + HeaderManager headerManager = new InMemoryHeaderManager(header); + SingleFileIndexStorageManager singleFileIndexStorageManager = new SingleFileIndexStorageManager(dbPath, headerManager, engineConfig, BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)); + IndexManager indexManager = new ClusterBPlusTreeIndexManager<>(degree, singleFileIndexStorageManager, new LongImmutableBinaryObjectWrapper()); + + + + AbstractTreeNode lastTreeNode = null; + for (long testIdentifier : testIdentifiers) { + lastTreeNode = indexManager.addIndex(tableId, testIdentifier, samplePointer); + } + + Assertions.assertTrue(lastTreeNode.isLeaf()); + Assertions.assertEquals(2, lastTreeNode.getKeyList(degree, PointerImmutableBinaryObjectWrapper.BYTES).size()); + Assertions.assertEquals(samplePointer.getPosition(), ((AbstractLeafTreeNode) lastTreeNode).getKeyValues(degree).next().value().getPosition()); + + StoredTreeStructureVerifier.testOrderedTreeStructure(singleFileIndexStorageManager, tableId, 1, degree); + } + + } + + + /** + * + * The B+Tree in this test will include numbers from [1-12] added respectively + * The shape of the tree will be like below, and the test verifies that + * The test validates the tree for 2 tables in same database + * 009 + * ├── . + * │ ├── 001 + * │ └── 002 + * ├── 003 + * │ ├── 003 + * │ ├── 004 + * │ └── 005 + * ├── 006 + * │ ├── 006 + * │ ├── 007 + * │ └── 008 + * ├── . + * │ ├── 009 + * │ └── 010 + * └── 011 + * ├── 011 + * └── 012 + */ + @Test + public void testMultiSplitAddIndex2() throws IOException, ExecutionException, InterruptedException, ImmutableBinaryObjectWrapper.InvalidBinaryObjectWrapperValue, IndexExistsException, InternalOperationException { + + List testIdentifiers = Arrays.asList(1L, 4L, 9L, 6L, 10L, 8L, 3L, 2L, 11L, 5L, 7L, 12L); + Pointer samplePointer = new Pointer(Pointer.TYPE_DATA, 100, 0); + HeaderManager headerManager = new InMemoryHeaderManager(header); + SingleFileIndexStorageManager singleFileIndexStorageManager = new SingleFileIndexStorageManager(dbPath, headerManager, engineConfig, BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)); + IndexManager indexManager = new ClusterBPlusTreeIndexManager<>(degree, singleFileIndexStorageManager, new LongImmutableBinaryObjectWrapper()); + + IndexFileDescriptor indexFileDescriptor = new IndexFileDescriptor( + AsynchronousFileChannel.open( + Path.of(dbPath.toString(), String.format("%s.%d", INDEX_FILE_NAME, 0)), + StandardOpenOption.READ, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE + ), + headerManager, + engineConfig + ); + + for (int tableId = 1; tableId <= 2; tableId++){ + + AbstractTreeNode lastTreeNode = null; + for (long testIdentifier : testIdentifiers) { + lastTreeNode = indexManager.addIndex(tableId, testIdentifier, samplePointer); + indexFileDescriptor.describe(new LongImmutableBinaryObjectWrapper()); + } + + Assertions.assertTrue(lastTreeNode.isLeaf()); + Assertions.assertEquals(2, lastTreeNode.getKeyList(degree, PointerImmutableBinaryObjectWrapper.BYTES).size()); + Assertions.assertEquals(samplePointer.getPosition(), ((AbstractLeafTreeNode) lastTreeNode).getKeyValues(degree).next().value().getPosition()); + + StoredTreeStructureVerifier.testUnOrderedTreeStructure1(singleFileIndexStorageManager, tableId, 1, degree); + + } + + } + +} diff --git a/src/test/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManagerTestCase.java b/src/test/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManagerTestCase.java new file mode 100644 index 0000000..ed9eb85 --- /dev/null +++ b/src/test/java/com/github/sepgh/testudo/storage/SingleFileIndexStorageManagerTestCase.java @@ -0,0 +1,221 @@ +package com.github.sepgh.testudo.storage; + +import com.github.sepgh.testudo.EngineConfig; +import com.github.sepgh.testudo.index.Pointer; +import com.github.sepgh.testudo.index.tree.node.AbstractTreeNode; +import com.github.sepgh.testudo.index.tree.node.InternalTreeNode; +import com.github.sepgh.testudo.index.tree.node.NodeFactory; +import com.github.sepgh.testudo.index.tree.node.cluster.LeafClusterTreeNode; +import com.github.sepgh.testudo.index.tree.node.data.ImmutableBinaryObjectWrapper; +import com.github.sepgh.testudo.index.tree.node.data.LongImmutableBinaryObjectWrapper; +import com.github.sepgh.testudo.index.tree.node.data.PointerImmutableBinaryObjectWrapper; +import com.github.sepgh.testudo.storage.header.Header; +import com.github.sepgh.testudo.storage.header.HeaderManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collections; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.github.sepgh.testudo.index.tree.node.AbstractTreeNode.*; +import static com.github.sepgh.testudo.index.tree.node.AbstractTreeNode.Type.INTERNAL; +import static com.github.sepgh.testudo.storage.SingleFileIndexStorageManager.INDEX_FILE_NAME; + +public class SingleFileIndexStorageManagerTestCase { + private Path dbPath; + private EngineConfig engineConfig; + private Header header; + private int degree = 1; + private final byte[] singleKeyLeafNodeRepresentation = { + ((byte) (0x00 | ROOT_BIT | TYPE_LEAF_NODE_BIT)), // Leaf + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, // Key 1 + + // >> Start pointer to child 1 + Pointer.TYPE_DATA, // type + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // position + 0x00, 0x00, 0x00, 0x01, // chunk + // >> End pointer to child 1 + }; + private final byte[] singleKeyInternalNodeRepresentation = { + ((byte) (0x00 | ROOT_BIT | TYPE_INTERNAL_NODE_BIT)), // Not leaf + + // >> Start pointer to child 1 + Pointer.TYPE_NODE, // type + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // position + 0x00, 0x00, 0x00, 0x01, // chunk + // >> End pointer to child 1 + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, // Key + + // >> Start pointer to child 2 + Pointer.TYPE_NODE, // type + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // position + 0x00, 0x00, 0x00, 0x02, // chunk + // >> End pointer to child 2 + }; + + @BeforeEach + public void setUp() throws IOException { + dbPath = Files.createTempDirectory("TEST_SingleFileIndexStorageManagerTestCase"); + engineConfig = EngineConfig.builder() + .bTreeDegree(degree) + .bTreeGrowthNodeAllocationCount(1) + .build(); + engineConfig.setBTreeMaxFileSize(3L * BTreeSizeCalculator.getClusteredBPlusTreeSize(1, LongImmutableBinaryObjectWrapper.BYTES)); + + byte[] writingBytes = new byte[BTreeSizeCalculator.getClusteredBPlusTreeSize(1, LongImmutableBinaryObjectWrapper.BYTES) * 2]; + System.arraycopy(singleKeyLeafNodeRepresentation, 0, writingBytes, 0, singleKeyLeafNodeRepresentation.length); + System.arraycopy(singleKeyInternalNodeRepresentation, 0, writingBytes, BTreeSizeCalculator.getClusteredBPlusTreeSize(1, LongImmutableBinaryObjectWrapper.BYTES), singleKeyInternalNodeRepresentation.length); + Path indexPath = Path.of(dbPath.toString(), String.format("%s.bin", INDEX_FILE_NAME)); + Files.write(indexPath, writingBytes, StandardOpenOption.WRITE, StandardOpenOption.CREATE); + + header = Header.builder() + .database("sample") + .tables( + Collections.singletonList( + Header.Table.builder() + .id(1) + .name("test") + .chunks( + Collections.singletonList( + Header.IndexChunk.builder() + .chunk(0) + .offset(0) + .build() + ) + ) + .initialized(true) + .build() + ) + ) + .build(); + + Assertions.assertTrue(header.getTableOfId(1).isPresent()); + Assertions.assertTrue(header.getTableOfId(1).get().getIndexChunk(0).isPresent()); + } + + @AfterEach + public void destroy() throws IOException { + Path indexPath0 = Path.of(dbPath.toString(), String.format("%s.bin", INDEX_FILE_NAME)); + Files.delete(indexPath0); + } + + @Test + public void canReadNodeSuccessfully() throws ExecutionException, InterruptedException, IOException { + + HeaderManager headerManager = new InMemoryHeaderManager(header); + NodeFactory.ClusterNodeFactory nodeFactory = new NodeFactory.ClusterNodeFactory<>(new LongImmutableBinaryObjectWrapper()); + + + SingleFileIndexStorageManager singleFileIndexStorageManager = new SingleFileIndexStorageManager(dbPath, headerManager, engineConfig, BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)); + try { + CompletableFuture future = singleFileIndexStorageManager.readNode(1, 0, 0); + + IndexStorageManager.NodeData nodeData = future.get(); + Assertions.assertEquals(BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.bytes().length); + + AbstractTreeNode treeNode = nodeFactory.fromBytes(nodeData.bytes()); + + Iterator keys = treeNode.getKeys(2, PointerImmutableBinaryObjectWrapper.BYTES); + + Assertions.assertTrue(treeNode.isRoot()); + Assertions.assertTrue(keys.hasNext()); + Assertions.assertEquals(15, keys.next()); + + + future = singleFileIndexStorageManager.readNode(1, BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), 0); + nodeData = future.get(); + Assertions.assertEquals(BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.bytes().length); + + treeNode = nodeFactory.fromBytes(nodeData.bytes()); + + Iterator> children = ((InternalTreeNode) treeNode).getChildPointers(2); + + Assertions.assertTrue(children.hasNext()); + InternalTreeNode.ChildPointers childPointers = children.next(); + Assertions.assertTrue(childPointers.getLeft().isNodePointer()); + Assertions.assertEquals(1, childPointers.getLeft().getPosition()); + Assertions.assertEquals(1, childPointers.getLeft().getChunk()); + } finally { + singleFileIndexStorageManager.close(); + } + + } + + @Test + public void canReadAndUpdateNodeSuccessfully() throws IOException, ExecutionException, InterruptedException { + HeaderManager headerManager = new InMemoryHeaderManager(header); + NodeFactory.ClusterNodeFactory nodeFactory = new NodeFactory.ClusterNodeFactory<>(new LongImmutableBinaryObjectWrapper()); + + + SingleFileIndexStorageManager singleFileIndexStorageManager = new SingleFileIndexStorageManager(dbPath, headerManager, engineConfig, BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)); + try { + CompletableFuture future = singleFileIndexStorageManager.readNode(1, 0, 0); + + IndexStorageManager.NodeData nodeData = future.get(); + Assertions.assertEquals(BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.bytes().length); + + LeafClusterTreeNode leafTreeNode = (LeafClusterTreeNode) nodeFactory.fromBytes(nodeData.bytes()); + leafTreeNode.setKeyValue(0, new LeafClusterTreeNode.KeyValue(10L, new Pointer(Pointer.TYPE_DATA, 100, 100))); + + singleFileIndexStorageManager.updateNode(1, leafTreeNode.getData(), nodeData.pointer()).get(); + + future = singleFileIndexStorageManager.readNode(1, 0, 0); + nodeData = future.get(); + leafTreeNode = (LeafClusterTreeNode) nodeFactory.fromBytes(nodeData.bytes()); + + Assertions.assertTrue(leafTreeNode.getKeyValues(2).hasNext()); + + Assertions.assertEquals(10L, leafTreeNode.getKeyValueList(2).get(0).key()); + Assertions.assertEquals(100, leafTreeNode.getKeyValueList(2).get(0).value().getChunk()); + Assertions.assertEquals(100, leafTreeNode.getKeyValueList(2).get(0).value().getPosition()); + + } catch (ImmutableBinaryObjectWrapper.InvalidBinaryObjectWrapperValue e) { + throw new RuntimeException(e); + } finally { + singleFileIndexStorageManager.close(); + } + } + + @Test + public void canWriteNewNodeAndAllocate() throws IOException, ExecutionException, InterruptedException { + HeaderManager headerManager = new InMemoryHeaderManager(header); + NodeFactory.ClusterNodeFactory nodeFactory = new NodeFactory.ClusterNodeFactory<>(new LongImmutableBinaryObjectWrapper()); + + SingleFileIndexStorageManager singleFileIndexStorageManager = new SingleFileIndexStorageManager(dbPath, headerManager, engineConfig, BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES)); + try { + + byte[] emptyNode = singleFileIndexStorageManager.getEmptyNode(); + AbstractTreeNode baseClusterTreeNode = nodeFactory.fromBytes(emptyNode, INTERNAL); + baseClusterTreeNode.setAsRoot(); + + IndexStorageManager.NodeData nodeData = singleFileIndexStorageManager.writeNewNode(1, baseClusterTreeNode.getData()).get(); + Assertions.assertEquals(BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.bytes().length); + Assertions.assertEquals(2L * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.pointer().getPosition()); + Assertions.assertEquals(0, nodeData.pointer().getChunk()); + + nodeData = singleFileIndexStorageManager.writeNewNode(1, baseClusterTreeNode.getData()).get(); + Assertions.assertEquals(BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.bytes().length); + Assertions.assertEquals(3L * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.pointer().getPosition()); + Assertions.assertEquals(0, nodeData.pointer().getChunk()); + + nodeData = singleFileIndexStorageManager.writeNewNode(1, baseClusterTreeNode.getData()).get(); + Assertions.assertEquals(BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.bytes().length); + Assertions.assertEquals(4L * BTreeSizeCalculator.getClusteredBPlusTreeSize(degree, LongImmutableBinaryObjectWrapper.BYTES), nodeData.pointer().getPosition()); + Assertions.assertEquals(0, nodeData.pointer().getChunk()); + + } finally { + singleFileIndexStorageManager.close(); + } + } + +}