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 4f2e79e6cedaa..31dd76a0bc33d 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 @@ -795,7 +795,7 @@ private class SingleWriter implements Writer SingleWriter( InternalTreeLogic treeLogic ) { - this.structurePropagation = new StructurePropagation<>( layout.newKey(), layout.newKey() ); + this.structurePropagation = new StructurePropagation<>( layout.newKey(), layout.newKey(), layout.newKey() ); this.treeLogic = treeLogic; } @@ -843,8 +843,9 @@ else if ( structurePropagation.hasMidChildUpdate ) checkOutOfBounds( cursor ); } - private void setRoot( long rootId ) + private void setRoot( long rootPointer ) { + long rootId = GenSafePointerPair.pointer( rootPointer ); GBPTree.this.setRoot( rootId, unstableGeneration ); treeLogic.initialize( cursor ); } diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java index 425fdb89f01e0..bff232333d8b7 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java @@ -27,6 +27,10 @@ import static org.neo4j.index.internal.gbptree.KeySearch.isHit; import static org.neo4j.index.internal.gbptree.KeySearch.positionOf; +import static org.neo4j.index.internal.gbptree.StructurePropagation.KeyReplaceStrategy.BUBBLE; +import static org.neo4j.index.internal.gbptree.StructurePropagation.KeyReplaceStrategy.REPLACE; +import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_MID_CHILD; +import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_RIGHT_CHILD; /** * Implementation of GB+ tree insert/remove algorithms. @@ -352,9 +356,7 @@ void insert( PageCursor cursor, StructurePropagation structurePropagation, if ( structurePropagation.hasMidChildUpdate ) { - structurePropagation.hasMidChildUpdate = false; - bTreeNode.setChildAt( - cursor, structurePropagation.midChild, pos, stableGeneration, unstableGeneration ); + updateMidChild( cursor, structurePropagation, pos, stableGeneration, unstableGeneration ); } if ( structurePropagation.hasRightKeyInsert ) { @@ -405,7 +407,7 @@ private void insertInInternal( PageCursor cursor, StructurePropagation stru KEY primKey, long rightChild, long stableGeneration, long unstableGeneration ) throws IOException { - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); if ( keyCount < bTreeNode.internalMaxKeyCount() ) { @@ -608,7 +610,7 @@ private void insertInLeaf( PageCursor cursor, StructurePropagation structur VALUE mergedValue = valueMerger.merge( readValue, value ); if ( mergedValue != null ) { - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); // simple, just write the merged value right in there bTreeNode.setValueAt( cursor, mergedValue, pos ); @@ -616,7 +618,7 @@ private void insertInLeaf( PageCursor cursor, StructurePropagation structur return; // No split has occurred } - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); if ( keyCount < bTreeNode.leafMaxKeyCount() ) @@ -794,11 +796,11 @@ private void splitLeaf( PageCursor cursor, StructurePropagation structurePr bTreeNode.setRightSibling( cursor, newRight, stableGeneration, unstableGeneration ); } - private void copyKeysAndValues( PageCursor cursor, int fromPos, PageCursor rightCursor, int toPos, int count ) + private void copyKeysAndValues( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toPos, int count ) { - cursor.copyTo( bTreeNode.keyOffset( fromPos ), rightCursor, bTreeNode.keyOffset( toPos ), + fromCursor.copyTo( bTreeNode.keyOffset( fromPos ), toCursor, bTreeNode.keyOffset( toPos ), count * bTreeNode.keySize() ); - cursor.copyTo( bTreeNode.valueOffset( fromPos ), rightCursor, bTreeNode.valueOffset( toPos ), + fromCursor.copyTo( bTreeNode.valueOffset( fromPos ), toCursor, bTreeNode.valueOffset( toPos ), count * bTreeNode.valueSize() ); } @@ -837,9 +839,11 @@ VALUE remove( PageCursor cursor, StructurePropagation structurePropagation, return null; } - while ( structurePropagation.hasMidChildUpdate || + while ( structurePropagation.hasLeftChildUpdate || + structurePropagation.hasMidChildUpdate || + structurePropagation.hasRightChildUpdate || structurePropagation.hasLeftKeyReplace || - structurePropagation.hasLeftChildUpdate ) + structurePropagation.hasRightKeyReplace ) { int pos = levels[currentLevel].childPos; if ( !popLevel( cursor ) ) @@ -848,24 +852,12 @@ VALUE remove( PageCursor cursor, StructurePropagation structurePropagation, break; } - if ( structurePropagation.hasMidChildUpdate ) - { - structurePropagation.hasMidChildUpdate = false; - bTreeNode.setChildAt( cursor, structurePropagation.midChild, pos, - stableGeneration, unstableGeneration ); - } - if ( structurePropagation.hasLeftKeyReplace && levels[currentLevel].covers( structurePropagation.leftKey ) ) - { - structurePropagation.hasLeftKeyReplace = false; - replaceKeyInInternal( cursor, structurePropagation, structurePropagation.leftKey, pos - 1, - stableGeneration, unstableGeneration ); - } if ( structurePropagation.hasLeftChildUpdate ) { structurePropagation.hasLeftChildUpdate = false; if ( pos == 0 ) { - updateRightMostChildInLeftSibling( cursor, structurePropagation.leftChild, + updateRightmostChildInLeftSibling( cursor, structurePropagation.leftChild, stableGeneration, unstableGeneration ); } else @@ -874,12 +866,216 @@ VALUE remove( PageCursor cursor, StructurePropagation structurePropagation, stableGeneration, unstableGeneration ); } } + + if ( structurePropagation.hasMidChildUpdate ) + { + updateMidChild( cursor, structurePropagation, pos, stableGeneration, unstableGeneration ); + } + + if ( structurePropagation.hasRightChildUpdate ) + { + structurePropagation.hasRightChildUpdate = false; + int keyCount = bTreeNode.keyCount( cursor ); + if ( pos == keyCount ) + { + updateLeftmostChildInRightSibling( cursor, structurePropagation.rightChild, + stableGeneration, unstableGeneration ); + } + else + { + bTreeNode.setChildAt( cursor, structurePropagation.rightChild, pos + 1, + stableGeneration, unstableGeneration ); + } + } + + if ( structurePropagation.hasLeftKeyReplace && + levels[currentLevel].covers( structurePropagation.leftKey ) ) + { + structurePropagation.hasLeftKeyReplace = false; + switch ( structurePropagation.keyReplaceStrategy ) + { + case REPLACE: + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.setKeyAt( cursor, structurePropagation.leftKey, pos - 1 ); + break; + case BUBBLE: + replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos - 1, + stableGeneration, unstableGeneration ); + break; + default: + throw new IllegalArgumentException( "Unknown KeyReplaceStrategy " + + structurePropagation.keyReplaceStrategy ); + } + } + + if ( structurePropagation.hasRightKeyReplace && + levels[currentLevel].covers( structurePropagation.rightKey ) ) + { + structurePropagation.hasRightKeyReplace = false; + switch ( structurePropagation.keyReplaceStrategy ) + { + case REPLACE: + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.setKeyAt( cursor, structurePropagation.rightKey, pos ); + break; + case BUBBLE: + replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos, + stableGeneration, unstableGeneration ); + break; + default: + throw new IllegalArgumentException( "Unknown KeyReplaceStrategy " + + structurePropagation.keyReplaceStrategy ); + } + } + } + + if ( currentLevel <= 0 ) + { + tryShrinkTree( cursor, structurePropagation, stableGeneration, unstableGeneration ); } return into; } - private void updateRightMostChildInLeftSibling( PageCursor cursor, long child, long stableGeneration, + private void tryShrinkTree( PageCursor cursor, StructurePropagation structurePropagation, + long stableGeneration, long unstableGeneration ) throws IOException + { + // New root will be propagated out. If rootKeyCount is 0 we can shrink the tree. + int rootKeyCount = bTreeNode.keyCount( cursor ); + + while ( rootKeyCount == 0 && TreeNode.isInternal( cursor ) ) + { + long oldRoot = cursor.getCurrentPageId(); + long onlyChildOfRoot = bTreeNode.childAt( cursor, 0, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( onlyChildOfRoot, false ); + + structurePropagation.hasMidChildUpdate = true; + structurePropagation.midChild = onlyChildOfRoot; + + idProvider.releaseId( stableGeneration, unstableGeneration, oldRoot ); + bTreeNode.goTo( cursor, "child", onlyChildOfRoot ); + + rootKeyCount = bTreeNode.keyCount( cursor ); + } + } + + private void updateMidChild( PageCursor cursor, StructurePropagation structurePropagation, int childPos, + long stableGeneration, long unstableGeneration ) + { + structurePropagation.hasMidChildUpdate = false; + bTreeNode.setChildAt( cursor, structurePropagation.midChild, childPos, + stableGeneration, unstableGeneration ); + } + + private void replaceKeyByBubbleRightmostFromSubtree( PageCursor cursor, + StructurePropagation structurePropagation, int subtreePosition, + long stableGeneration, long unstableGeneration ) throws IOException + { + long currentPageId = cursor.getCurrentPageId(); + long subtree = bTreeNode.childAt( cursor, subtreePosition, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( subtree, false ); + + bTreeNode.goTo( cursor, "child", subtree ); + boolean foundKeyBelow = bubbleRightmostKeyRecursive( cursor, structurePropagation, currentPageId, + stableGeneration, unstableGeneration ); + + // Propagate structurePropagation from below + if ( structurePropagation.hasMidChildUpdate ) + { + updateMidChild( cursor, structurePropagation, subtreePosition, stableGeneration, unstableGeneration ); + } + + if ( foundKeyBelow ) + { + // A key has been bubble up to us. + // It's in structurePropagation.leftKey and should be inserted in subtreePosition. + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.setKeyAt( cursor, structurePropagation.bubbleKey, subtreePosition ); + } + else + { + // No key could be found in subtree, it's completely empty and can be removed. + // We shift keys and children in this internal node to the left (potentially creating new version of this + // node). + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration); + int keyCount = bTreeNode.keyCount( cursor ); + simplyRemoveFromInternal( cursor, keyCount, subtreePosition, subtreePosition ); + } + } + + private boolean bubbleRightmostKeyRecursive( PageCursor cursor, StructurePropagation structurePropagation, + long previousNode, long stableGeneration, long unstableGeneration ) throws IOException + { + try + { + if ( TreeNode.isLeaf( cursor ) ) + { + // Base case + return false; + } + // Recursive case + long currentPageId = cursor.getCurrentPageId(); + int keyCount = bTreeNode.keyCount( cursor ); + long rightmostSubtree = bTreeNode.childAt( cursor, keyCount, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( rightmostSubtree, false ); + + bTreeNode.goTo( cursor, "child", rightmostSubtree ); + + + boolean foundKeyBelow = bubbleRightmostKeyRecursive( cursor, structurePropagation, currentPageId, + stableGeneration, unstableGeneration ); + + // Propagate structurePropagation from below + if ( structurePropagation.hasMidChildUpdate ) + { + updateMidChild( cursor, structurePropagation, keyCount, stableGeneration, unstableGeneration ); + } + + if ( foundKeyBelow ) + { + return true; + } + + if ( keyCount == 0 ) + { + // This subtree does not contain anything any more + // Repoint sibling and add to freelist and return false + connectLeftAndRightSibling( cursor, stableGeneration, unstableGeneration ); + idProvider.releaseId( stableGeneration, unstableGeneration, currentPageId ); + return false; + } + + // Create new version of node, save rightmost key in structurePropagation, remove rightmost key and child + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.keyAt( cursor, structurePropagation.bubbleKey, keyCount - 1 ); + simplyRemoveFromInternal( cursor, keyCount, keyCount - 1, keyCount ); + + return true; + } + finally + { + bTreeNode.goTo( cursor, "back to previous node", previousNode ); + } + } + + private int simplyRemoveFromInternal( PageCursor cursor, int keyCount, int keyPos, int childPos ) + { + // Remove key and child + bTreeNode.removeKeyAt( cursor, keyPos, keyCount ); + bTreeNode.removeChildAt( cursor, childPos, keyCount ); + + // Decrease key count + int newKeyCount = keyCount - 1; + bTreeNode.setKeyCount( cursor, newKeyCount ); + return newKeyCount; + } + + private void updateRightmostChildInLeftSibling( PageCursor cursor, long childPointer, long stableGeneration, long unstableGeneration ) throws IOException { long currentPageId = cursor.getCurrentPageId(); @@ -889,18 +1085,23 @@ private void updateRightMostChildInLeftSibling( PageCursor cursor, long child, l bTreeNode.goTo( cursor, "left sibling", leftSibling ); int keyCount = bTreeNode.keyCount( cursor ); - bTreeNode.setChildAt( cursor, child, keyCount, stableGeneration, unstableGeneration ); + bTreeNode.setChildAt( cursor, childPointer, keyCount, stableGeneration, unstableGeneration ); bTreeNode.goTo( cursor, "back to current from left sibling", currentPageId ); } - private void replaceKeyInInternal( PageCursor cursor, StructurePropagation structurePropagation, KEY key, - int keyPosition, long stableGeneration, long unstableGeneration ) throws IOException + private void updateLeftmostChildInRightSibling( PageCursor cursor, long childPointer, long stableGeneration, + long unstableGeneration ) throws IOException { - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, - stableGeneration, unstableGeneration ); + long currentPageId = cursor.getCurrentPageId(); + long rightSibling = bTreeNode.rightSibling( cursor, stableGeneration, unstableGeneration ); + // Left sibling is not allowed to be NO_NODE here because that means there is a child node with no parent + PointerChecking.checkPointer( rightSibling, false ); + + bTreeNode.goTo( cursor, "right sibling", rightSibling ); + bTreeNode.setChildAt( cursor, childPointer, 0, stableGeneration, unstableGeneration ); - bTreeNode.setKeyAt( cursor, key, keyPosition ); + bTreeNode.goTo( cursor, "back to current from right sibling", currentPageId ); } /** @@ -933,7 +1134,7 @@ private boolean removeFromLeaf( PageCursor cursor, StructurePropagation str return false; } - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); keyCount = simplyRemoveFromLeaf( cursor, into, keyCount, pos ); @@ -951,6 +1152,8 @@ private void underflowInLeaf( PageCursor cursor, StructurePropagation struc { long leftSibling = bTreeNode.leftSibling( cursor, stableGeneration, unstableGeneration ); PointerChecking.checkPointer( leftSibling, true ); + long rightSibling = bTreeNode.rightSibling( cursor, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( rightSibling, true ); if ( TreeNode.isNode( leftSibling ) ) { @@ -966,8 +1169,108 @@ private void underflowInLeaf( PageCursor cursor, StructurePropagation struc StructurePropagation.UPDATE_LEFT_CHILD, stableGeneration, unstableGeneration ); rebalanceLeaf( cursor, leftSiblingCursor, structurePropagation, keyCount, leftSiblingKeyCount ); } + else + { + // No need to create new unstable version of left sibling. + // Parent pointer will be updated later. + mergeFromLeftSiblingLeaf( cursor, leftSiblingCursor, structurePropagation, keyCount, + leftSiblingKeyCount, stableGeneration, unstableGeneration ); + } } } + else if ( TreeNode.isNode( rightSibling ) ) + { + try ( PageCursor rightSiblingCursor = cursor.openLinkedCursor( + GenSafePointerPair.pointer( rightSibling ) ) ) + { + rightSiblingCursor.next(); + int rightSiblingKeyCount = bTreeNode.keyCount( rightSiblingCursor ); + + if ( keyCount + rightSiblingKeyCount <= bTreeNode.leafMaxKeyCount() ) + { + createUnstableVersionIfNeeded( rightSiblingCursor, structurePropagation, UPDATE_RIGHT_CHILD, + stableGeneration, unstableGeneration ); + mergeToRightSiblingLeaf( cursor, rightSiblingCursor, structurePropagation, keyCount, + rightSiblingKeyCount, stableGeneration, unstableGeneration); + } + } + } + } + + private void connectLeftAndRightSibling( PageCursor cursor, long stableGeneration, long unstableGeneration ) + throws IOException + { + long currentId = cursor.getCurrentPageId(); + long leftSibling = bTreeNode.leftSibling( cursor, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( leftSibling, true ); + long rightSibling = bTreeNode. rightSibling( cursor, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( rightSibling, true ); + if ( TreeNode.isNode( leftSibling ) ) + { + bTreeNode.goTo( cursor, "left sibling", leftSibling ); + bTreeNode.setRightSibling( cursor, rightSibling, stableGeneration, unstableGeneration ); + } + if ( TreeNode.isNode( rightSibling ) ) + { + bTreeNode.goTo( cursor, "right sibling", rightSibling ); + bTreeNode.setLeftSibling( cursor, leftSibling, stableGeneration, unstableGeneration ); + } + + bTreeNode.goTo( cursor, "back to origin after repointing siblings", currentId ); + } + + private void mergeToRightSiblingLeaf( PageCursor cursor, PageCursor rightSiblingCursor, + StructurePropagation structurePropagation, int keyCount, int rightSiblingKeyCount, + long stableGeneration, long unstableGeneration ) throws IOException + { + merge( cursor, keyCount, rightSiblingCursor, rightSiblingKeyCount, stableGeneration, unstableGeneration ); + + // Propagate change + // mid child has been merged into right child + // right key was separator key + structurePropagation.hasMidChildUpdate = true; + structurePropagation.midChild = rightSiblingCursor.getCurrentPageId(); + structurePropagation.hasRightKeyReplace = true; + structurePropagation.keyReplaceStrategy = BUBBLE; + rightSiblingKeyCount = bTreeNode.keyCount( rightSiblingCursor ); + bTreeNode.keyAt( rightSiblingCursor, structurePropagation.rightKey, rightSiblingKeyCount - 1 ); + } + + private void mergeFromLeftSiblingLeaf( PageCursor cursor, PageCursor leftSiblingCursor, + StructurePropagation structurePropagation, int keyCount, int leftSiblingKeyCount, + long stableGeneration, long unstableGeneration ) throws IOException + { + // Move stuff and update key count + merge( leftSiblingCursor, leftSiblingKeyCount, cursor, keyCount, stableGeneration, unstableGeneration ); + + // Propagate change + // left child has been merged into mid child + // left key was separator key + structurePropagation.hasLeftChildUpdate = true; + structurePropagation.leftChild = cursor.getCurrentPageId(); + structurePropagation.hasLeftKeyReplace = true; + structurePropagation.keyReplaceStrategy = BUBBLE; + bTreeNode.keyAt( cursor, structurePropagation.leftKey, 0 ); + } + + private void merge( PageCursor leftSiblingCursor, int leftSiblingKeyCount, PageCursor rightSiblingCursor, + int rightSiblingKeyCount, long stableGeneration, long unstableGeneration ) throws IOException + { + // Push keys in right sibling to the right + bTreeNode.insertKeySlotsAt( rightSiblingCursor, 0, leftSiblingKeyCount, rightSiblingKeyCount ); + bTreeNode.insertValueSlotsAt( rightSiblingCursor, 0, leftSiblingKeyCount, rightSiblingKeyCount ); + + // Move keys and values from left sibling to right sibling + copyKeysAndValues( leftSiblingCursor, 0, rightSiblingCursor, 0, leftSiblingKeyCount ); + bTreeNode.setKeyCount( rightSiblingCursor, rightSiblingKeyCount + leftSiblingKeyCount ); + + // Update new gen of left sibling to be right sibling + bTreeNode.setNewGen( leftSiblingCursor, rightSiblingCursor.getCurrentPageId(), + stableGeneration, unstableGeneration ); + + // Add left sibling to free list + connectLeftAndRightSibling( leftSiblingCursor, stableGeneration, unstableGeneration ); + idProvider.releaseId( stableGeneration, unstableGeneration, leftSiblingCursor.getCurrentPageId() ); } private void rebalanceLeaf( PageCursor cursor, PageCursor leftSiblingCursor, @@ -981,22 +1284,14 @@ private void rebalanceLeaf( PageCursor cursor, PageCursor leftSiblingCursor, bTreeNode.insertKeySlotsAt( cursor, 0, numberOfKeysToMove, keyCount ); bTreeNode.insertValueSlotsAt( cursor, 0, numberOfKeysToMove, keyCount ); - // Move keys from left sibling to right sibling - int sourceOffsetKey = bTreeNode.keyOffset( keyCountInLeftSiblingAfterRebalance ); - int targetOffsetKey = bTreeNode.keyOffset( 0 ); - int bytesToCopyKey = numberOfKeysToMove * bTreeNode.keySize(); - leftSiblingCursor.copyTo( sourceOffsetKey, cursor, targetOffsetKey, bytesToCopyKey ); + // Move keys and values from left sibling to right sibling + copyKeysAndValues( leftSiblingCursor, keyCountInLeftSiblingAfterRebalance, cursor, 0, numberOfKeysToMove ); bTreeNode.setKeyCount( cursor, keyCount + numberOfKeysToMove ); - - // Move values from left sibling to right sibling - int sourceOffsetValue = bTreeNode.valueOffset( keyCountInLeftSiblingAfterRebalance ); - int targetOffsetValue = bTreeNode.valueOffset( 0 ); - int bytesToCopyValue = numberOfKeysToMove * bTreeNode.keySize(); - leftSiblingCursor.copyTo( sourceOffsetValue, cursor, targetOffsetValue, bytesToCopyValue ); bTreeNode.setKeyCount( leftSiblingCursor, leftSiblingKeyCount - numberOfKeysToMove ); // Propagate change structurePropagation.hasLeftKeyReplace = true; + structurePropagation.keyReplaceStrategy = REPLACE; bTreeNode.keyAt( cursor, structurePropagation.leftKey, 0 ); } @@ -1023,6 +1318,13 @@ private int simplyRemoveFromLeaf( PageCursor cursor, VALUE into, int keyCount, i return newKeyCount; } + void applySecondPhaseUpdate( PageCursor cursor, StructurePropagation structurePropagation, + long stableGeneration, long unstableGeneration ) + { + // todo + throw new UnsupportedOperationException( "Implement me" ); + } + /** * Create a new node and copy content from current node (where {@code cursor} sits) if current node is not already * of {@code unstableGeneration}. diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java index 55c0d468f45ee..0e605fe029760 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java @@ -560,7 +560,7 @@ else if ( !saneRead() ) continue; // in the read loop above so that we can continue reading from next sibling } } - else if ( insideEndRange() ) + else if ( 0 <= pos && pos < keyCount && insideEndRange() ) { if ( isResultKey() ) { diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java index 1498c9e39390c..5357ba510d823 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java @@ -59,20 +59,26 @@ */ class StructurePropagation { + // First level of updates, used when bubbling up the tree boolean hasLeftChildUpdate; + boolean hasRightChildUpdate; boolean hasMidChildUpdate; boolean hasRightKeyInsert; boolean hasLeftKeyReplace; + boolean hasRightKeyReplace; final KEY leftKey; final KEY rightKey; + final KEY bubbleKey; long leftChild; long midChild; long rightChild; + KeyReplaceStrategy keyReplaceStrategy; - StructurePropagation( KEY leftKey, KEY rightKey ) + StructurePropagation( KEY leftKey, KEY rightKey, KEY bubbleKey ) { this.leftKey = leftKey; this.rightKey = rightKey; + this.bubbleKey = bubbleKey; } /** @@ -81,9 +87,11 @@ class StructurePropagation void clear() { hasLeftChildUpdate = false; + hasRightChildUpdate = false; hasMidChildUpdate = false; hasRightKeyInsert = false; hasLeftKeyReplace = false; + hasRightKeyReplace = false; } interface StructureUpdate @@ -100,4 +108,14 @@ interface StructureUpdate sp.hasMidChildUpdate = true; sp.midChild = childId; }; + + static final StructureUpdate UPDATE_RIGHT_CHILD = ( sp, childId ) -> { + sp.hasRightChildUpdate = true; + sp.rightChild = childId; + }; + + enum KeyReplaceStrategy + { + REPLACE, BUBBLE + } } 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 4d2005d4ca38e..da23975a32571 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 @@ -247,10 +247,10 @@ void removeKeyAt( PageCursor cursor, int pos, int keyCount ) removeSlotAt( cursor, pos, keyCount, keyOffset( 0 ), keySize ); } - private void removeSlotAt( PageCursor cursor, int pos, int keyCount, int baseOffset, int itemSize ) + private void removeSlotAt( PageCursor cursor, int pos, int itemCount, int baseOffset, int itemSize ) { for ( int posToMoveLeft = pos + 1, offset = baseOffset + posToMoveLeft * itemSize; - posToMoveLeft < keyCount; posToMoveLeft++, offset += itemSize ) + posToMoveLeft < itemCount; posToMoveLeft++, offset += itemSize ) { cursor.copyTo( offset, cursor, offset - itemSize, itemSize ); } @@ -299,6 +299,11 @@ void insertChildAt( PageCursor cursor, long child, int pos, int keyCount, setChildAt( cursor, child, pos, stableGeneration, unstableGeneration ); } + void removeChildAt( PageCursor cursor, int pos, int keyCount ) + { + removeSlotAt( cursor, pos, keyCount + 1, childOffset( 0 ), childSize() ); + } + void setChildAt( PageCursor cursor, long child, int pos, long stableGeneration, long unstableGeneration ) { cursor.setOffset( childOffset( pos ) ); 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 9ff17b34ad4a9..3b159a2705bd1 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 @@ -88,7 +88,8 @@ public void shouldDetectUnusedPages() throws Exception cursor.next( idProvider.acquireNewId( stableGeneration, unstableGeneration ) ); node.initializeLeaf( cursor, stableGeneration, unstableGeneration ); logic.initialize( cursor ); - StructurePropagation structure = new StructurePropagation<>( layout.newKey(), layout.newKey() ); + StructurePropagation structure = new StructurePropagation<>( layout.newKey(), layout.newKey(), + layout.newKey() ); MutableLong key = layout.newKey(); for ( int g = 0, k = 0; g < 3; g++ ) { diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java index 40fed6ea34c7a..a034ed5f8cd57 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java @@ -21,7 +21,6 @@ import org.apache.commons.lang3.mutable.MutableLong; import org.junit.After; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -138,8 +137,6 @@ public void shouldReadForwardCorrectlyWithConcurrentRemove() throws Throwable shouldReadCorrectlyWithConcurrentUpdates( testCoordinator ); } - @Ignore( "Test will leave tree nodes empty because merge is still missing in implementation. " + - "When seeking backwards this will cause infinite loops in SeekCursor." ) @Test public void shouldReadBackwardCorrectlyWithConcurrentRemove() throws Throwable { @@ -331,7 +328,7 @@ private List generateUpdatesForNextIteration() } else if ( toRemove.isEmpty() ) { - operation = new WriteOperation( toAdd.poll() ); + operation = new PutOperation( toAdd.poll() ); } else { @@ -342,7 +339,7 @@ else if ( toRemove.isEmpty() ) } else { - operation = new WriteOperation( toAdd.poll() ); + operation = new PutOperation( toAdd.poll() ); } } updateOperations.add( operation ); @@ -394,9 +391,9 @@ private abstract class UpdateOperation abstract boolean isInsert(); } - private class WriteOperation extends UpdateOperation + private class PutOperation extends UpdateOperation { - WriteOperation( long key ) + PutOperation( long key ) { super( key ); } 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 007d0e73ed9a2..cbfdb4ac2e5f9 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 @@ -71,7 +71,7 @@ public class InternalTreeLogicTest private final MutableLong readKey = new MutableLong(); private final MutableLong readValue = new MutableLong(); private final StructurePropagation structurePropagation = new StructurePropagation<>( - layout.newKey(), layout.newKey() ); + layout.newKey(), layout.newKey(), layout.newKey() ); private static long stableGen = GenSafePointer.MIN_GENERATION; private static long unstableGen = stableGen + 1; @@ -913,15 +913,23 @@ public void shouldCreateNewVersionWhenRemoveInStableLeaf() throws Exception initialize(); long targetLastId = id.lastId() + 3; // 2 splits and 1 new allocated root long i = 0; - for ( ; id.lastId() < targetLastId; i++ ) + for ( ; id.lastId() < targetLastId; i += 2 ) { insert( i, i ); } goTo( readCursor, rootId ); assertEquals( 2, keyCount() ); + long leftChild = childAt( readCursor, 0, stableGen, unstableGen ); long middleChild = childAt( readCursor, 1, stableGen, unstableGen ); long rightChild = childAt( readCursor, 2, stableGen, unstableGen ); + + // add some more keys to middleChild to not have remove trigger a merge + goTo( readCursor, middleChild ); + Long firstKeyInMiddleChild = keyAt( 0 ); + insert( firstKeyInMiddleChild + 1, firstKeyInMiddleChild + 1 ); + goTo( readCursor, rootId ); + assertSiblings( leftChild, middleChild, rightChild ); // WHEN 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 64fdc8a63965c..51e9ec08a1de7 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 @@ -66,7 +66,7 @@ public long getAsLong() private final TreeNode node = new TreeNode<>( PAGE_SIZE, layout ); private final InternalTreeLogic treeLogic = new InternalTreeLogic<>( id, node, layout ); private final StructurePropagation structurePropagation = - new StructurePropagation<>( layout.newKey(), layout.newKey() ); + new StructurePropagation<>( layout.newKey(), layout.newKey(), layout.newKey() ); private final PageAwareByteArrayCursor cursor = new PageAwareByteArrayCursor( PAGE_SIZE ); private final int maxKeyCount = node.leafMaxKeyCount();