From d7d76171e661ded9aa7c300f6f7f1ff84ab6033d Mon Sep 17 00:00:00 2001 From: Anton Persson Date: Fri, 1 Dec 2017 15:27:01 +0100 Subject: [PATCH] Abstract TreeNode and TreeNodeFixedSize --- .../neo4j/index/internal/gbptree/GBPTree.java | 2 +- .../index/internal/gbptree/TreeNode.java | 376 ++-------------- .../internal/gbptree/TreeNodeFixedSize.java | 404 ++++++++++++++++++ .../gbptree/ConsistencyCheckerTest.java | 2 +- .../gbptree/CrashGenerationCleanerTest.java | 2 +- .../gbptree/InternalTreeLogicTest.java | 4 +- .../index/internal/gbptree/KeySearchTest.java | 2 +- .../internal/gbptree/SeekCursorTest.java | 2 +- .../index/internal/gbptree/TreeNodeTest.java | 4 +- 9 files changed, 451 insertions(+), 347 deletions(-) create mode 100644 community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java index ff87a14952e92..502360be1e0d2 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java @@ -425,7 +425,7 @@ public GBPTree( PageCache pageCache, File indexFile, Layout layout, i this.pagedFile = openOrCreate( pageCache, indexFile, tentativePageSize, layout ); this.pageSize = pagedFile.pageSize(); closed = false; - this.bTreeNode = new TreeNode<>( pageSize, layout ); + this.bTreeNode = new TreeNodeFixedSize<>( pageSize, layout ); this.freeList = new FreeListIdProvider( pagedFile, pageSize, rootId, FreeListIdProvider.NO_MONITOR ); this.writer = new SingleWriter( new InternalTreeLogic<>( freeList, bTreeNode, layout ) ); diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java index d33c7126713d6..e6cbb69b8885c 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java @@ -26,7 +26,6 @@ import static org.neo4j.index.internal.gbptree.GenerationSafePointerPair.NO_LOGICAL_POS; import static org.neo4j.index.internal.gbptree.GenerationSafePointerPair.read; -import static org.neo4j.index.internal.gbptree.TreeNode.Type.INTERNAL; /** * Methods to manipulate single tree node such as set and get header fields, @@ -65,7 +64,7 @@ * @param type of key * @param type of value */ -class TreeNode +abstract class TreeNode { enum Type { @@ -87,39 +86,17 @@ enum Type static final int BYTE_POS_SUCCESSOR = BYTE_POS_LEFTSIBLING + SIZE_PAGE_REFERENCE; static final int HEADER_LENGTH = BYTE_POS_SUCCESSOR + SIZE_PAGE_REFERENCE; - private static final byte LEAF_FLAG = 1; + static final byte LEAF_FLAG = 1; static final byte INTERNAL_FLAG = 0; static final long NO_NODE_FLAG = 0; + final Layout layout; private final int pageSize; - private final int internalMaxKeyCount; - private final int leafMaxKeyCount; - private final Layout layout; - - private final int keySize; - private final int valueSize; TreeNode( int pageSize, Layout layout ) { this.pageSize = pageSize; this.layout = layout; - this.keySize = layout.keySize(); - this.valueSize = layout.valueSize(); - this.internalMaxKeyCount = Math.floorDiv( pageSize - (HEADER_LENGTH + SIZE_PAGE_REFERENCE), - keySize + SIZE_PAGE_REFERENCE); - this.leafMaxKeyCount = Math.floorDiv( pageSize - HEADER_LENGTH, keySize + valueSize ); - - if ( internalMaxKeyCount < 2 ) - { - throw new MetadataMismatchException( - "For layout %s a page size of %d would only fit %d internal keys, minimum is 2", - layout, pageSize, internalMaxKeyCount ); - } - if ( leafMaxKeyCount < 2 ) - { - throw new MetadataMismatchException( "A page size of %d would only fit leaf keys, minimum is 2", - pageSize, leafMaxKeyCount ); - } } static byte nodeType( PageCursor cursor ) @@ -241,199 +218,55 @@ long pointerGeneration( PageCursor cursor, long readResult ) // BODY METHODS - KEY keyAt( PageCursor cursor, KEY into, int pos, Type type ) - { - cursor.setOffset( keyOffset( pos ) ); - layout.readKey( cursor, into ); - return into; - } + abstract KEY keyAt( PageCursor cursor, KEY into, int pos, Type type ); // Insert key without associated value. // Useful for internal nodes and testing. - void insertKeyAt( PageCursor cursor, KEY key, int pos, int keyCount, Type type ) - { - insertKeySlotsAt( cursor, pos, 1, keyCount ); - cursor.setOffset( keyOffset( pos ) ); - layout.writeKey( cursor, key ); - } + abstract void insertKeyAt( PageCursor cursor, KEY key, int pos, int keyCount, Type type ); - void insertKeyValueAt( PageCursor cursor, KEY key, VALUE value, int pos, int keyCount ) - { - insertKeyAt( cursor, key, pos, keyCount, Type.LEAF ); - insertValueAt( cursor, value, pos, keyCount ); - } + abstract void insertKeyValueAt( PageCursor cursor, KEY key, VALUE value, int pos, int keyCount ); // Remove key without removing associated value. // Useful for internal nodes and testing. - void removeKeyAt( PageCursor cursor, int pos, int keyCount, Type type ) - { - removeSlotAt( cursor, pos, keyCount, keyOffset( 0 ), keySize ); - } + abstract void removeKeyAt( PageCursor cursor, int pos, int keyCount, Type type ); - void removeKeyValueAt( PageCursor cursor, int pos, int keyCount ) - { - removeKeyAt( cursor, pos, keyCount, Type.LEAF ); - removeValueAt( cursor, pos, keyCount ); - } + abstract void removeKeyValueAt( PageCursor cursor, int pos, int keyCount ); - void setKeyAt( PageCursor cursor, KEY key, int pos, Type type ) - { - cursor.setOffset( keyOffset( pos ) ); - layout.writeKey( cursor, key ); - } + abstract void setKeyAt( PageCursor cursor, KEY key, int pos, Type type ); - VALUE valueAt( PageCursor cursor, VALUE value, int pos ) - { - cursor.setOffset( valueOffset( pos ) ); - layout.readValue( cursor, value ); - return value; - } + abstract VALUE valueAt( PageCursor cursor, VALUE value, int pos ); - void setValueAt( PageCursor cursor, VALUE value, int pos ) - { - cursor.setOffset( valueOffset( pos ) ); - layout.writeValue( cursor, value ); - } + abstract void setValueAt( PageCursor cursor, VALUE value, int pos ); - long childAt( PageCursor cursor, int pos, long stableGeneration, long unstableGeneration ) - { - cursor.setOffset( childOffset( pos ) ); - return read( cursor, stableGeneration, unstableGeneration, pos ); - } + abstract long childAt( PageCursor cursor, int pos, long stableGeneration, long unstableGeneration ); - void insertChildAt( PageCursor cursor, long child, int pos, int keyCount, - long stableGeneration, long unstableGeneration ) - { - insertChildSlotsAt( cursor, pos, 1, keyCount ); - setChildAt( cursor, child, pos, stableGeneration, unstableGeneration ); - } + abstract void insertChildAt( PageCursor cursor, long child, int pos, int keyCount, + long stableGeneration, long unstableGeneration ); - void removeChildAt( PageCursor cursor, int pos, int keyCount ) - { - removeSlotAt( cursor, pos, keyCount + 1, childOffset( 0 ), childSize() ); - } + abstract void removeChildAt( PageCursor cursor, int pos, int keyCount ); - void setChildAt( PageCursor cursor, long child, int pos, long stableGeneration, long unstableGeneration ) - { - cursor.setOffset( childOffset( pos ) ); - writeChild( cursor, child, stableGeneration, unstableGeneration ); - } + abstract void setChildAt( PageCursor cursor, long child, int pos, long stableGeneration, long unstableGeneration ); - private static void writeChild( PageCursor cursor, long child, long stableGeneration, long unstableGeneration ) + static void writeChild( PageCursor cursor, long child, long stableGeneration, long unstableGeneration ) { GenerationSafePointerPair.write( cursor, child, stableGeneration, unstableGeneration ); } - private void insertKeyValueSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) - { - insertKeySlotsAt( cursor, pos, numberOfSlots, keyCount ); - insertValueSlotsAt( cursor, pos, numberOfSlots, keyCount ); - } - - // Always insert together with key. Use insertKeyValueAt - private void insertValueAt( PageCursor cursor, VALUE value, int pos, int keyCount ) - { - insertValueSlotsAt( cursor, pos, 1, keyCount ); - setValueAt( cursor, value, pos ); - } - - // Always insert together with key. Use removeKeyValueAt - private void removeValueAt( PageCursor cursor, int pos, int keyCount ) - { - removeSlotAt( cursor, pos, keyCount, valueOffset( 0 ), valueSize ); - } - - /** - * Moves items (key/value/child) one step to the right, which means rewriting all items of the particular type - * from pos - itemCount. - * itemCount is keyCount for key and value, but keyCount+1 for children. - */ - private static void insertSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int itemCount, int baseOffset, - int itemSize ) - { - for ( int posToMoveRight = itemCount - 1, offset = baseOffset + posToMoveRight * itemSize; - posToMoveRight >= pos; posToMoveRight--, offset -= itemSize ) - { - cursor.copyTo( offset, cursor, offset + itemSize * numberOfSlots, itemSize ); - } - } - - private static void removeSlotAt( PageCursor cursor, int pos, int itemCount, int baseOffset, int itemSize ) - { - for ( int posToMoveLeft = pos + 1, offset = baseOffset + posToMoveLeft * itemSize; - posToMoveLeft < itemCount; posToMoveLeft++, offset += itemSize ) - { - cursor.copyTo( offset, cursor, offset - itemSize, itemSize ); - } - } - - private void insertKeySlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) - { - insertSlotsAt( cursor, pos, numberOfSlots, keyCount, keyOffset( 0 ), keySize ); - } - - private void insertValueSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) - { - insertSlotsAt( cursor, pos, numberOfSlots, keyCount, valueOffset( 0 ), valueSize ); - } - - private void insertChildSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) - { - insertSlotsAt( cursor, pos, numberOfSlots, keyCount + 1, childOffset( 0 ), childSize() ); - } + abstract int internalMaxKeyCount(); - int internalMaxKeyCount() - { - return internalMaxKeyCount; - } - - int leafMaxKeyCount() - { - return leafMaxKeyCount; - } + abstract int leafMaxKeyCount(); // HELPERS - boolean reasonableKeyCount( int keyCount ) - { - return keyCount >= 0 && keyCount <= Math.max( internalMaxKeyCount(), leafMaxKeyCount() ); - } - - private int keyOffset( int pos ) - { - return HEADER_LENGTH + pos * keySize; - } - - private int valueOffset( int pos ) - { - return HEADER_LENGTH + leafMaxKeyCount * keySize + pos * valueSize; - } + abstract boolean reasonableKeyCount( int keyCount ); - int childOffset( int pos ) - { - return HEADER_LENGTH + internalMaxKeyCount * keySize + pos * SIZE_PAGE_REFERENCE; - } + abstract int childOffset( int pos ); static boolean isNode( long node ) { return GenerationSafePointerPair.pointer( node ) != NO_NODE_FLAG; } - private int keySize() - { - return keySize; - } - - private int valueSize() - { - return valueSize; - } - - private static int childSize() - { - return SIZE_PAGE_REFERENCE; - } - Comparator keyComparator() { return layout; @@ -448,36 +281,23 @@ static void goTo( PageCursor cursor, String messageOnError, long nodeId ) @Override public String toString() { - return "TreeNode[pageSize:" + pageSize + ", internalMax:" + internalMaxKeyCount + - ", leafMax:" + leafMaxKeyCount + ", keySize:" + keySize + ", valueSize:" + valueSize + "]"; + return "TreeNode[pageSize:" + pageSize + ", internalMax:" + internalMaxKeyCount() + ", leafMax:" + leafMaxKeyCount() + ", " + + additionalToString() + "]"; } + abstract String additionalToString(); + /* SPLIT, MERGE AND REBALANCE */ - boolean internalOverflow( int keyCount ) - { - return keyCount > internalMaxKeyCount(); - } + abstract boolean internalOverflow( int keyCount ); - boolean leafOverflow( int keyCount ) - { - return keyCount > leafMaxKeyCount(); - } + abstract boolean leafOverflow( int keyCount ); - boolean leafUnderflow( int keyCount ) - { - return keyCount < (leafMaxKeyCount() + 1) / 2; - } + abstract boolean leafUnderflow( int keyCount ); - boolean canRebalanceLeaves( int leftKeyCount, int rightKeyCount ) - { - return leftKeyCount + rightKeyCount >= leafMaxKeyCount(); - } + abstract boolean canRebalanceLeaves( int leftKeyCount, int rightKeyCount ); - boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ) - { - return leftKeyCount + rightKeyCount <= leafMaxKeyCount(); - } + abstract boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ); /** * Performs the entry moving part of split in leaf. @@ -486,42 +306,9 @@ boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ) * * Key count is updated. */ - void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, KEY newKey, - VALUE newValue, int middlePos ) - { - if ( insertPos < middlePos ) - { - // v-------v copy - // before _,_,_,_,_,_,_,_,_,_ - // insert _,_,_,X,_,_,_,_,_,_,_ - // middle ^ - copyKeysAndValues( leftCursor, middlePos - 1, rightCursor, 0, rightKeyCount ); - insertKeyValueAt( leftCursor, newKey, newValue, insertPos, middlePos - 1 ); - } - else - { - // v---v first copy - // v-v second copy - // before _,_,_,_,_,_,_,_,_,_ - // insert _,_,_,_,_,_,_,_,X,_,_ - // middle ^ - int countBeforePos = insertPos - middlePos; - if ( countBeforePos > 0 ) - { - // first copy - copyKeysAndValues( leftCursor, middlePos, rightCursor, 0, countBeforePos ); - } - insertKeyValueAt( rightCursor, newKey, newValue, countBeforePos, countBeforePos ); - int countAfterPos = leftKeyCount - insertPos; - if ( countAfterPos > 0 ) - { - // second copy - copyKeysAndValues( leftCursor, insertPos, rightCursor, countBeforePos + 1, countAfterPos ); - } - } - TreeNode.setKeyCount( leftCursor, middlePos ); - TreeNode.setKeyCount( rightCursor, rightKeyCount ); - } + abstract void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, + KEY newKey, + VALUE newValue, int middlePos ); /** * Performs the entry moving part of split in internal. @@ -530,103 +317,16 @@ void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor rightCurso * * Key count is updated. */ - void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, KEY newKey, - long newRightChild, int middlePos, long stableGeneration, long unstableGeneration ) - { - if ( insertPos < middlePos ) - { - // v-------v copy - // before key _,_,_,_,_,_,_,_,_,_ - // before child -,-,-,-,-,-,-,-,-,-,- - // insert key _,_,X,_,_,_,_,_,_,_,_ - // insert child -,-,-,x,-,-,-,-,-,-,-,- - // middle key ^ - - leftCursor.copyTo( keyOffset( middlePos ), rightCursor, keyOffset( 0 ), rightKeyCount * keySize() ); - leftCursor.copyTo( childOffset( middlePos ), rightCursor, childOffset( 0 ), (rightKeyCount + 1) * childSize() ); - insertKeyAt( leftCursor, newKey, insertPos, middlePos - 1, INTERNAL ); - insertChildAt( leftCursor, newRightChild, insertPos + 1, middlePos - 1, stableGeneration, unstableGeneration ); - } - else - { - // pos > middlePos - // v-v first copy - // v-v-v second copy - // before key _,_,_,_,_,_,_,_,_,_ - // before child -,-,-,-,-,-,-,-,-,-,- - // insert key _,_,_,_,_,_,_,X,_,_,_ - // insert child -,-,-,-,-,-,-,-,x,-,-,- - // middle key ^ - - // pos == middlePos - // first copy - // v-v-v-v-v second copy - // before key _,_,_,_,_,_,_,_,_,_ - // before child -,-,-,-,-,-,-,-,-,-,- - // insert key _,_,_,_,_,X,_,_,_,_,_ - // insert child -,-,-,-,-,-,x,-,-,-,-,- - // middle key ^ - - // Keys - int countBeforePos = insertPos - (middlePos + 1); - // ... first copy - if ( countBeforePos > 0 ) - { - leftCursor.copyTo( keyOffset( middlePos + 1 ), rightCursor, keyOffset( 0 ), countBeforePos * keySize() ); - } - // ... insert - if ( countBeforePos >= 0 ) - { - insertKeyAt( rightCursor, newKey, countBeforePos, countBeforePos, INTERNAL ); - } - // ... second copy - int countAfterPos = leftKeyCount - insertPos; - if ( countAfterPos > 0 ) - { - leftCursor.copyTo( keyOffset( insertPos ), rightCursor, keyOffset( countBeforePos + 1 ), countAfterPos * keySize() ); - } - - // Children - countBeforePos = insertPos - middlePos; - // ... first copy - if ( countBeforePos > 0 ) - { - // first copy - leftCursor.copyTo( childOffset( middlePos + 1 ), rightCursor, childOffset( 0 ), countBeforePos * childSize() ); - } - // ... insert - insertChildAt( rightCursor, newRightChild, countBeforePos, countBeforePos, stableGeneration, unstableGeneration ); - // ... second copy - if ( countAfterPos > 0 ) - { - leftCursor.copyTo( childOffset( insertPos + 1 ), rightCursor, childOffset( countBeforePos + 1 ), - countAfterPos * childSize() ); - } - } - TreeNode.setKeyCount( leftCursor, middlePos ); - TreeNode.setKeyCount( rightCursor, rightKeyCount ); - } + abstract void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, + KEY newKey, + long newRightChild, int middlePos, long stableGeneration, long unstableGeneration ); /** * Move all rightmost keys and values in left node from given position to right node. * * Key count is NOT updated. */ - void moveKeyValuesFromLeftToRight( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, - int fromPosInLeftNode ) - { - int numberOfKeysToMove = leftKeyCount - fromPosInLeftNode; + abstract void moveKeyValuesFromLeftToRight( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, + int fromPosInLeftNode ); - // Push keys and values in right sibling to the right - insertKeyValueSlotsAt( rightCursor, 0, numberOfKeysToMove, rightKeyCount ); - - // Move keys and values from left sibling to right sibling - copyKeysAndValues( leftCursor, fromPosInLeftNode, rightCursor, 0, numberOfKeysToMove ); - } - - private void copyKeysAndValues( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toPos, int count ) - { - fromCursor.copyTo( keyOffset( fromPos ), toCursor, keyOffset( toPos ), count * keySize() ); - fromCursor.copyTo( valueOffset( fromPos ), toCursor, valueOffset( toPos ),count * valueSize() ); - } } diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java new file mode 100644 index 0000000000000..b490eb49d79d9 --- /dev/null +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java @@ -0,0 +1,404 @@ +package org.neo4j.index.internal.gbptree; + +import org.neo4j.io.pagecache.PageCursor; + +import static org.neo4j.index.internal.gbptree.GenerationSafePointerPair.read; +import static org.neo4j.index.internal.gbptree.TreeNode.Type.INTERNAL; + +public class TreeNodeFixedSize extends TreeNode +{ + private final int internalMaxKeyCount; + private final int leafMaxKeyCount; + private final int keySize; + private final int valueSize; + + TreeNodeFixedSize( int pageSize, Layout layout ) + { + super( pageSize, layout ); + this.keySize = layout.keySize(); + this.valueSize = layout.valueSize(); + this.internalMaxKeyCount = Math.floorDiv( pageSize - (HEADER_LENGTH + SIZE_PAGE_REFERENCE), + keySize + SIZE_PAGE_REFERENCE); + this.leafMaxKeyCount = Math.floorDiv( pageSize - HEADER_LENGTH, keySize + valueSize ); + + if ( internalMaxKeyCount < 2 ) + { + throw new MetadataMismatchException( + "For layout %s a page size of %d would only fit %d internal keys, minimum is 2", + layout, pageSize, internalMaxKeyCount ); + } + if ( leafMaxKeyCount < 2 ) + { + throw new MetadataMismatchException( "A page size of %d would only fit leaf keys, minimum is 2", + pageSize, leafMaxKeyCount ); + } + } + + /** + * Moves items (key/value/child) one step to the right, which means rewriting all items of the particular type + * from pos - itemCount. + * itemCount is keyCount for key and value, but keyCount+1 for children. + */ + private static void insertSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int itemCount, int baseOffset, + int itemSize ) + { + for ( int posToMoveRight = itemCount - 1, offset = baseOffset + posToMoveRight * itemSize; + posToMoveRight >= pos; posToMoveRight--, offset -= itemSize ) + { + cursor.copyTo( offset, cursor, offset + itemSize * numberOfSlots, itemSize ); + } + } + + private static void removeSlotAt( PageCursor cursor, int pos, int itemCount, int baseOffset, int itemSize ) + { + for ( int posToMoveLeft = pos + 1, offset = baseOffset + posToMoveLeft * itemSize; + posToMoveLeft < itemCount; posToMoveLeft++, offset += itemSize ) + { + cursor.copyTo( offset, cursor, offset - itemSize, itemSize ); + } + } + + private static int childSize() + { + return SIZE_PAGE_REFERENCE; + } + + @Override + KEY keyAt( PageCursor cursor, KEY into, int pos, Type type ) + { + cursor.setOffset( keyOffset( pos ) ); + layout.readKey( cursor, into ); + return into; + } + + // Insert key without associated value. + // Useful for internal nodes and testing. + @Override + void insertKeyAt( PageCursor cursor, KEY key, int pos, int keyCount, Type type ) + { + insertKeySlotsAt( cursor, pos, 1, keyCount ); + cursor.setOffset( keyOffset( pos ) ); + layout.writeKey( cursor, key ); + } + + @Override + void insertKeyValueAt( PageCursor cursor, KEY key, VALUE value, int pos, int keyCount ) + { + insertKeyAt( cursor, key, pos, keyCount, Type.LEAF ); + insertValueAt( cursor, value, pos, keyCount ); + } + + // Remove key without removing associated value. + // Useful for internal nodes and testing. + @Override + void removeKeyAt( PageCursor cursor, int pos, int keyCount, Type type ) + { + removeSlotAt( cursor, pos, keyCount, keyOffset( 0 ), keySize ); + } + + @Override + void removeKeyValueAt( PageCursor cursor, int pos, int keyCount ) + { + removeKeyAt( cursor, pos, keyCount, Type.LEAF ); + removeValueAt( cursor, pos, keyCount ); + } + + @Override + void setKeyAt( PageCursor cursor, KEY key, int pos, Type type ) + { + cursor.setOffset( keyOffset( pos ) ); + layout.writeKey( cursor, key ); + } + + @Override + VALUE valueAt( PageCursor cursor, VALUE value, int pos ) + { + cursor.setOffset( valueOffset( pos ) ); + layout.readValue( cursor, value ); + return value; + } + + @Override + void setValueAt( PageCursor cursor, VALUE value, int pos ) + { + cursor.setOffset( valueOffset( pos ) ); + layout.writeValue( cursor, value ); + } + + @Override + long childAt( PageCursor cursor, int pos, long stableGeneration, long unstableGeneration ) + { + cursor.setOffset( childOffset( pos ) ); + return read( cursor, stableGeneration, unstableGeneration, pos ); + } + + @Override + void insertChildAt( PageCursor cursor, long child, int pos, int keyCount, + long stableGeneration, long unstableGeneration ) + { + insertChildSlotsAt( cursor, pos, 1, keyCount ); + setChildAt( cursor, child, pos, stableGeneration, unstableGeneration ); + } + + @Override + void removeChildAt( PageCursor cursor, int pos, int keyCount ) + { + removeSlotAt( cursor, pos, keyCount + 1, childOffset( 0 ), childSize() ); + } + + @Override + void setChildAt( PageCursor cursor, long child, int pos, long stableGeneration, long unstableGeneration ) + { + cursor.setOffset( childOffset( pos ) ); + writeChild( cursor, child, stableGeneration, unstableGeneration ); + } + + private void insertKeyValueSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) + { + insertKeySlotsAt( cursor, pos, numberOfSlots, keyCount ); + insertValueSlotsAt( cursor, pos, numberOfSlots, keyCount ); + } + + // Always insert together with key. Use insertKeyValueAt + private void insertValueAt( PageCursor cursor, VALUE value, int pos, int keyCount ) + { + insertValueSlotsAt( cursor, pos, 1, keyCount ); + setValueAt( cursor, value, pos ); + } + + // Always insert together with key. Use removeKeyValueAt + private void removeValueAt( PageCursor cursor, int pos, int keyCount ) + { + removeSlotAt( cursor, pos, keyCount, valueOffset( 0 ), valueSize ); + } + + private void insertKeySlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) + { + insertSlotsAt( cursor, pos, numberOfSlots, keyCount, keyOffset( 0 ), keySize ); + } + + private void insertValueSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) + { + insertSlotsAt( cursor, pos, numberOfSlots, keyCount, valueOffset( 0 ), valueSize ); + } + + private void insertChildSlotsAt( PageCursor cursor, int pos, int numberOfSlots, int keyCount ) + { + insertSlotsAt( cursor, pos, numberOfSlots, keyCount + 1, childOffset( 0 ), childSize() ); + } + + @Override + int internalMaxKeyCount() + { + return internalMaxKeyCount; + } + + @Override + int leafMaxKeyCount() + { + return leafMaxKeyCount; + } + + @Override + boolean reasonableKeyCount( int keyCount ) + { + return keyCount >= 0 && keyCount <= Math.max( internalMaxKeyCount(), leafMaxKeyCount() ); + } + + private int keyOffset( int pos ) + { + return HEADER_LENGTH + pos * keySize; + } + + private int valueOffset( int pos ) + { + return HEADER_LENGTH + leafMaxKeyCount * keySize + pos * valueSize; + } + + @Override + int childOffset( int pos ) + { + return HEADER_LENGTH + internalMaxKeyCount * keySize + pos * SIZE_PAGE_REFERENCE; + } + + @Override + String additionalToString() + { + return "keySize:" + keySize() + ", valueSize:" + valueSize; + } + + private int keySize() + { + return keySize; + } + + private int valueSize() + { + return valueSize; + } + + @Override + boolean internalOverflow( int keyCount ) + { + return keyCount > internalMaxKeyCount(); + } + + @Override + boolean leafOverflow( int keyCount ) + { + return keyCount > leafMaxKeyCount(); + } + + @Override + boolean leafUnderflow( int keyCount ) + { + return keyCount < (leafMaxKeyCount() + 1) / 2; + } + + @Override + boolean canRebalanceLeaves( int leftKeyCount, int rightKeyCount ) + { + return leftKeyCount + rightKeyCount >= leafMaxKeyCount(); + } + + @Override + boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ) + { + return leftKeyCount + rightKeyCount <= leafMaxKeyCount(); + } + + @Override + void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, KEY newKey, + VALUE newValue, int middlePos ) + { + if ( insertPos < middlePos ) + { + // v-------v copy + // before _,_,_,_,_,_,_,_,_,_ + // insert _,_,_,X,_,_,_,_,_,_,_ + // middle ^ + copyKeysAndValues( leftCursor, middlePos - 1, rightCursor, 0, rightKeyCount ); + insertKeyValueAt( leftCursor, newKey, newValue, insertPos, middlePos - 1 ); + } + else + { + // v---v first copy + // v-v second copy + // before _,_,_,_,_,_,_,_,_,_ + // insert _,_,_,_,_,_,_,_,X,_,_ + // middle ^ + int countBeforePos = insertPos - middlePos; + if ( countBeforePos > 0 ) + { + // first copy + copyKeysAndValues( leftCursor, middlePos, rightCursor, 0, countBeforePos ); + } + insertKeyValueAt( rightCursor, newKey, newValue, countBeforePos, countBeforePos ); + int countAfterPos = leftKeyCount - insertPos; + if ( countAfterPos > 0 ) + { + // second copy + copyKeysAndValues( leftCursor, insertPos, rightCursor, countBeforePos + 1, countAfterPos ); + } + } + TreeNode.setKeyCount( leftCursor, middlePos ); + TreeNode.setKeyCount( rightCursor, rightKeyCount ); + } + + @Override + void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, KEY newKey, + long newRightChild, int middlePos, long stableGeneration, long unstableGeneration ) + { + if ( insertPos < middlePos ) + { + // v-------v copy + // before key _,_,_,_,_,_,_,_,_,_ + // before child -,-,-,-,-,-,-,-,-,-,- + // insert key _,_,X,_,_,_,_,_,_,_,_ + // insert child -,-,-,x,-,-,-,-,-,-,-,- + // middle key ^ + + leftCursor.copyTo( keyOffset( middlePos ), rightCursor, keyOffset( 0 ), rightKeyCount * keySize() ); + leftCursor.copyTo( childOffset( middlePos ), rightCursor, childOffset( 0 ), (rightKeyCount + 1) * childSize() ); + insertKeyAt( leftCursor, newKey, insertPos, middlePos - 1, INTERNAL ); + insertChildAt( leftCursor, newRightChild, insertPos + 1, middlePos - 1, stableGeneration, unstableGeneration ); + } + else + { + // pos > middlePos + // v-v first copy + // v-v-v second copy + // before key _,_,_,_,_,_,_,_,_,_ + // before child -,-,-,-,-,-,-,-,-,-,- + // insert key _,_,_,_,_,_,_,X,_,_,_ + // insert child -,-,-,-,-,-,-,-,x,-,-,- + // middle key ^ + + // pos == middlePos + // first copy + // v-v-v-v-v second copy + // before key _,_,_,_,_,_,_,_,_,_ + // before child -,-,-,-,-,-,-,-,-,-,- + // insert key _,_,_,_,_,X,_,_,_,_,_ + // insert child -,-,-,-,-,-,x,-,-,-,-,- + // middle key ^ + + // Keys + int countBeforePos = insertPos - (middlePos + 1); + // ... first copy + if ( countBeforePos > 0 ) + { + leftCursor.copyTo( keyOffset( middlePos + 1 ), rightCursor, keyOffset( 0 ), countBeforePos * keySize() ); + } + // ... insert + if ( countBeforePos >= 0 ) + { + insertKeyAt( rightCursor, newKey, countBeforePos, countBeforePos, INTERNAL ); + } + // ... second copy + int countAfterPos = leftKeyCount - insertPos; + if ( countAfterPos > 0 ) + { + leftCursor.copyTo( keyOffset( insertPos ), rightCursor, keyOffset( countBeforePos + 1 ), countAfterPos * keySize() ); + } + + // Children + countBeforePos = insertPos - middlePos; + // ... first copy + if ( countBeforePos > 0 ) + { + // first copy + leftCursor.copyTo( childOffset( middlePos + 1 ), rightCursor, childOffset( 0 ), countBeforePos * childSize() ); + } + // ... insert + insertChildAt( rightCursor, newRightChild, countBeforePos, countBeforePos, stableGeneration, unstableGeneration ); + // ... second copy + if ( countAfterPos > 0 ) + { + leftCursor.copyTo( childOffset( insertPos + 1 ), rightCursor, childOffset( countBeforePos + 1 ), + countAfterPos * childSize() ); + } + } + TreeNode.setKeyCount( leftCursor, middlePos ); + TreeNode.setKeyCount( rightCursor, rightKeyCount ); + } + + @Override + void moveKeyValuesFromLeftToRight( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, + int fromPosInLeftNode ) + { + int numberOfKeysToMove = leftKeyCount - fromPosInLeftNode; + + // Push keys and values in right sibling to the right + insertKeyValueSlotsAt( rightCursor, 0, numberOfKeysToMove, rightKeyCount ); + + // Move keys and values from left sibling to right sibling + copyKeysAndValues( leftCursor, fromPosInLeftNode, rightCursor, 0, numberOfKeysToMove ); + } + + private void copyKeysAndValues( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toPos, int count ) + { + fromCursor.copyTo( keyOffset( fromPos ), toCursor, keyOffset( toPos ), count * keySize() ); + fromCursor.copyTo( valueOffset( fromPos ), toCursor, valueOffset( toPos ),count * valueSize() ); + } +} diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java index 4307bf0ce812a..b5fbcd52a657d 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java @@ -77,7 +77,7 @@ public void shouldDetectUnusedPages() throws Exception // GIVEN int pageSize = 256; Layout layout = new SimpleLongLayout(); - TreeNode node = new TreeNode<>( pageSize, layout ); + TreeNode node = new TreeNodeFixedSize<>( pageSize, layout ); long stableGeneration = GenerationSafePointer.MIN_GENERATION; long unstableGeneration = stableGeneration + 1; SimpleIdProvider idProvider = new SimpleIdProvider(); diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/CrashGenerationCleanerTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/CrashGenerationCleanerTest.java index 67bbf89bc1919..ce8ac9a30517f 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/CrashGenerationCleanerTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/CrashGenerationCleanerTest.java @@ -431,7 +431,7 @@ void corrupt( PageCursor pageCursor, CorruptableTreeNode node, int stableGenerat int unstableGeneration, int crashGeneration ); } - class CorruptableTreeNode extends TreeNode + class CorruptableTreeNode extends TreeNodeFixedSize { CorruptableTreeNode( int pageSize, Layout layout ) { diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java index 00e1893aeb021..45c744f40f76e 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java @@ -64,7 +64,7 @@ public class InternalTreeLogicTest private final SimpleIdProvider id = new SimpleIdProvider(); private final Layout layout = new SimpleLongLayout(); - private final TreeNode node = new TreeNode<>( pageSize, layout ); + private final TreeNode node = new TreeNodeFixedSize<>( pageSize, layout ); private final InternalTreeLogic treeLogic = new InternalTreeLogic<>( id, node, layout ); private final PageAwareByteArrayCursor cursor = new PageAwareByteArrayCursor( pageSize ); @@ -1531,7 +1531,7 @@ private void printTree() throws IOException { long currentPageId = cursor.getCurrentPageId(); cursor.next( rootId ); - new TreePrinter<>( node, layout, stableGeneration, unstableGeneration ).printTree( cursor, cursor, System.out, true, true, true ); + new TreePrinter<>( node, layout, stableGeneration, unstableGeneration ).printTree( cursor, cursor, System.out, false, false, false ); cursor.next( currentPageId ); } diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/KeySearchTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/KeySearchTest.java index 0ab382af12b19..9e6518ef52cf6 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/KeySearchTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/KeySearchTest.java @@ -43,7 +43,7 @@ public class KeySearchTest private static final int PAGE_SIZE = 512; private final PageCursor cursor = wrap( new byte[PAGE_SIZE], 0, PAGE_SIZE ); private final Layout layout = new SimpleLongLayout(); - private final TreeNode node = new TreeNode<>( PAGE_SIZE, layout ); + private final TreeNode node = new TreeNodeFixedSize<>( PAGE_SIZE, layout ); private final MutableLong readKey = layout.newKey(); private final MutableLong searchKey = layout.newKey(); private final MutableLong insertKey = layout.newKey(); diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java index 9310407ab4263..e4be4fa9259b7 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java @@ -69,7 +69,7 @@ public long getAsLong() private final SimpleIdProvider id = new SimpleIdProvider(); private final Layout layout = new SimpleLongLayout(); - private final TreeNode node = new TreeNode<>( PAGE_SIZE, layout ); + private final TreeNode node = new TreeNodeFixedSize<>( PAGE_SIZE, layout ); private final InternalTreeLogic treeLogic = new InternalTreeLogic<>( id, node, layout ); private final StructurePropagation structurePropagation = new StructurePropagation<>( layout.newKey(), layout.newKey(), layout.newKey() ); diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/TreeNodeTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/TreeNodeTest.java index 1a51bd59e1f84..e02a8f5c28ce8 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/TreeNodeTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/TreeNodeTest.java @@ -49,7 +49,7 @@ public class TreeNodeTest private static final int PAGE_SIZE = 512; private final PageCursor cursor = new PageAwareByteArrayCursor( PAGE_SIZE ); private final Layout layout = new SimpleLongLayout(); - private final TreeNode node = new TreeNode<>( PAGE_SIZE, layout ); + private final TreeNode node = new TreeNodeFixedSize<>( PAGE_SIZE, layout ); @Rule public final RandomRule random = new RandomRule(); @@ -528,7 +528,7 @@ public void shouldAssertPageSizeBigEnoughForAtLeastTwoKeys() throws Exception // WHEN try { - new TreeNode<>( TreeNode.HEADER_LENGTH + layout.keySize() + layout.valueSize(), layout ); + new TreeNodeFixedSize<>( TreeNode.HEADER_LENGTH + layout.keySize() + layout.valueSize(), layout ); fail( "Should have failed" ); } catch ( MetadataMismatchException e )