Skip to content

Commit

Permalink
TreeNodeDynamicSize impl part 10 - overwriteKeyInternal
Browse files Browse the repository at this point in the history
Dynamic size TreeNode can now replace keys.
This is done by either simply overwriting old key with new
if sizes are equal or removing old key and inserting new.
This can cause overflow and split in internal and that is handled.

Include
(TreeNodeDynamicSize) `removeKeyAndChild` now update deadspace correctly
(TreeNodeDynamicSize) implement `setKeyAtInternal`
(TreeNodeDynamicSize) `internalOverflow` also consider deadspace

Also
(ConsistencyChecker) Better resilience for null in range
(InternalTreeLogic) `overwriteKeyInternal`
(TreeNode) make `defragmentInternal` package private
(TreeNode) setKeyAt is explicit for internal nodes
(TreeNode) `internalOverflow` return `Overflow`
  • Loading branch information
burqen committed Jan 16, 2018
1 parent 9dbd33a commit 579b58c
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 62 deletions.
Expand Up @@ -351,7 +351,7 @@ private void assertSubtrees( PageCursor cursor, KeyRange<KEY> range, int keyCoun
throws IOException throws IOException
{ {
long pageId = cursor.getCurrentPageId(); long pageId = cursor.getCurrentPageId();
KEY prev = layout.newKey(); KEY prev = null;
KeyRange<KEY> childRange; KeyRange<KEY> childRange;


// Check children, all except the last one // Check children, all except the last one
Expand Down Expand Up @@ -380,6 +380,10 @@ private void assertSubtrees( PageCursor cursor, KeyRange<KEY> range, int keyCoun


TreeNode.goTo( cursor, "parent", pageId ); TreeNode.goTo( cursor, "parent", pageId );


if ( pos == 0 )
{
prev = layout.newKey();
}
layout.copyKey( readKey, prev ); layout.copyKey( readKey, prev );
pos++; pos++;
} }
Expand Down Expand Up @@ -523,7 +527,15 @@ boolean inRange( KEY key )


KeyRange<KEY> restrictLeft( KEY left ) KeyRange<KEY> restrictLeft( KEY left )
{ {
if ( fromInclusive == null || comparator.compare( fromInclusive, left ) < 0 ) if ( fromInclusive == null )
{
return new KeyRange<>( comparator, left, toExclusive, layout, this );
}
if ( left == null )
{
return new KeyRange<>( comparator, fromInclusive, toExclusive, layout, this );
}
if ( comparator.compare( fromInclusive, left ) < 0 )
{ {
return new KeyRange<>( comparator, left, toExclusive, layout, this ); return new KeyRange<>( comparator, left, toExclusive, layout, this );
} }
Expand All @@ -532,7 +544,15 @@ KeyRange<KEY> restrictLeft( KEY left )


KeyRange<KEY> restrictRight( KEY right ) KeyRange<KEY> restrictRight( KEY right )
{ {
if ( toExclusive == null || comparator.compare( toExclusive, right ) > 0 ) if ( toExclusive == null )
{
return new KeyRange<>( comparator, fromInclusive, right, layout, this );
}
if ( right == null )
{
return new KeyRange<>( comparator, fromInclusive, toExclusive, layout, this );
}
if ( comparator.compare( toExclusive, right ) > 0 )
{ {
return new KeyRange<>( comparator, fromInclusive, right, layout, this ); return new KeyRange<>( comparator, fromInclusive, right, layout, this );
} }
Expand Down
Expand Up @@ -31,6 +31,7 @@
import static org.neo4j.index.internal.gbptree.PointerChecking.assertNoSuccessor; import static org.neo4j.index.internal.gbptree.PointerChecking.assertNoSuccessor;
import static org.neo4j.index.internal.gbptree.StructurePropagation.KeyReplaceStrategy.BUBBLE; 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.KeyReplaceStrategy.REPLACE;
import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_LEFT_CHILD;
import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_MID_CHILD; import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_MID_CHILD;
import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_RIGHT_CHILD; import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_RIGHT_CHILD;
import static org.neo4j.index.internal.gbptree.TreeNode.Overflow.NEED_DEFRAG; import static org.neo4j.index.internal.gbptree.TreeNode.Overflow.NEED_DEFRAG;
Expand Down Expand Up @@ -400,7 +401,14 @@ private void insertInInternal( PageCursor cursor, StructurePropagation<KEY> stru
createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD,
stableGeneration, unstableGeneration ); stableGeneration, unstableGeneration );


if ( bTreeNode.internalOverflow( cursor, keyCount, primKey ) ) doInsertInInternal( cursor, structurePropagation, keyCount, primKey, rightChild, stableGeneration, unstableGeneration );
}

private void doInsertInInternal( PageCursor cursor, StructurePropagation<KEY> structurePropagation, int keyCount, KEY primKey,
long rightChild, long stableGeneration, long unstableGeneration ) throws IOException
{
Overflow overflow = bTreeNode.internalOverflow( cursor, keyCount, primKey );
if ( overflow == YES )
{ {
// Overflow // Overflow
// We will overwrite rightKey in structurePropagation, so copy it over to a place holder // We will overwrite rightKey in structurePropagation, so copy it over to a place holder
Expand All @@ -410,6 +418,11 @@ private void insertInInternal( PageCursor cursor, StructurePropagation<KEY> stru
return; return;
} }


if ( overflow == NEED_DEFRAG )
{
bTreeNode.defragmentInternal( cursor );
}

// No overflow // No overflow
int pos = positionOf( search( cursor, INTERNAL, primKey, readKey, keyCount ) ); int pos = positionOf( search( cursor, INTERNAL, primKey, readKey, keyCount ) );
bTreeNode.insertKeyAndRightChildAt( cursor, primKey, rightChild, pos, keyCount, stableGeneration, unstableGeneration ); bTreeNode.insertKeyAndRightChildAt( cursor, primKey, rightChild, pos, keyCount, stableGeneration, unstableGeneration );
Expand Down Expand Up @@ -498,7 +511,7 @@ private void insertInLeaf( PageCursor cursor, StructurePropagation<KEY> structur


createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration );


doInsert( cursor, structurePropagation, key, value, pos, keyCount, stableGeneration, unstableGeneration ); doInsertInLeaf( cursor, structurePropagation, key, value, pos, keyCount, stableGeneration, unstableGeneration );
} }


private void overwriteValue( PageCursor cursor, StructurePropagation<KEY> structurePropagation, KEY key, VALUE value, private void overwriteValue( PageCursor cursor, StructurePropagation<KEY> structurePropagation, KEY key, VALUE value,
Expand All @@ -513,18 +526,14 @@ private void overwriteValue( PageCursor cursor, StructurePropagation<KEY> struct
// simple, just write the merged value right in there // simple, just write the merged value right in there
boolean couldOverwrite = bTreeNode.setValueAt( cursor, mergedValue, pos ); boolean couldOverwrite = bTreeNode.setValueAt( cursor, mergedValue, pos );
//noinspection StatementWithEmptyBody //noinspection StatementWithEmptyBody
if ( couldOverwrite ) if ( !couldOverwrite )
{
// Fine we are done
}
else
{ {
// Value could not be overwritten in a simple way because they differ in size. // Value could not be overwritten in a simple way because they differ in size.
// Delete old value // Delete old value
bTreeNode.removeKeyValueAt( cursor, pos, keyCount ); bTreeNode.removeKeyValueAt( cursor, pos, keyCount );
TreeNode.setKeyCount( cursor, keyCount - 1 ); TreeNode.setKeyCount( cursor, keyCount - 1 );
boolean didSplit = boolean didSplit =
doInsert( cursor, structurePropagation, key, mergedValue, pos, keyCount - 1, stableGeneration, unstableGeneration ); doInsertInLeaf( cursor, structurePropagation, key, mergedValue, pos, keyCount - 1, stableGeneration, unstableGeneration );
if ( !didSplit && bTreeNode.leafUnderflow( cursor, keyCount ) ) if ( !didSplit && bTreeNode.leafUnderflow( cursor, keyCount ) )
{ {
underflowInLeaf( cursor, structurePropagation, keyCount, stableGeneration, unstableGeneration ); underflowInLeaf( cursor, structurePropagation, keyCount, stableGeneration, unstableGeneration );
Expand All @@ -533,7 +542,7 @@ private void overwriteValue( PageCursor cursor, StructurePropagation<KEY> struct
} }
} }


private boolean doInsert( PageCursor cursor, StructurePropagation<KEY> structurePropagation, KEY key, VALUE value, int pos, private boolean doInsertInLeaf( PageCursor cursor, StructurePropagation<KEY> structurePropagation, KEY key, VALUE value, int pos,
int keyCount, long stableGeneration, long unstableGeneration ) throws IOException int keyCount, long stableGeneration, long unstableGeneration ) throws IOException
{ {
Overflow overflow = bTreeNode.leafOverflow( cursor, keyCount, key, value ); Overflow overflow = bTreeNode.leafOverflow( cursor, keyCount, key, value );
Expand Down Expand Up @@ -759,16 +768,26 @@ private void handleStructureChanges( PageCursor cursor, StructurePropagation<KEY
} }
} }


// Insert before replace because replace can lead to split and another insert in next level.
// Replace can only come from rebalance on lower levels and because we do no rebalance among
// internal nodes we will only ever have one replace on our way up.
if ( structurePropagation.hasRightKeyInsert )
{
structurePropagation.hasRightKeyInsert = false;
insertInInternal( cursor, structurePropagation, TreeNode.keyCount( cursor ),
structurePropagation.rightKey, structurePropagation.rightChild,
stableGeneration, unstableGeneration );
}

if ( structurePropagation.hasLeftKeyReplace && if ( structurePropagation.hasLeftKeyReplace &&
levels[currentLevel].covers( structurePropagation.leftKey ) ) levels[currentLevel].covers( structurePropagation.leftKey ) )
{ {
structurePropagation.hasLeftKeyReplace = false; structurePropagation.hasLeftKeyReplace = false;
switch ( structurePropagation.keyReplaceStrategy ) switch ( structurePropagation.keyReplaceStrategy )
{ {
case REPLACE: case REPLACE:
createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, overwriteKeyInternal( cursor, structurePropagation, structurePropagation.leftKey, pos - 1,
stableGeneration, unstableGeneration ); stableGeneration, unstableGeneration );
bTreeNode.setKeyAt( cursor, structurePropagation.leftKey, pos - 1, INTERNAL );
break; break;
case BUBBLE: case BUBBLE:
replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos - 1, replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos - 1,
Expand All @@ -787,9 +806,8 @@ private void handleStructureChanges( PageCursor cursor, StructurePropagation<KEY
switch ( structurePropagation.keyReplaceStrategy ) switch ( structurePropagation.keyReplaceStrategy )
{ {
case REPLACE: case REPLACE:
createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, overwriteKeyInternal( cursor, structurePropagation, structurePropagation.rightKey, pos,
stableGeneration, unstableGeneration ); stableGeneration, unstableGeneration );
bTreeNode.setKeyAt( cursor, structurePropagation.rightKey, pos, INTERNAL );
break; break;
case BUBBLE: case BUBBLE:
replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos, replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos,
Expand All @@ -800,14 +818,24 @@ private void handleStructureChanges( PageCursor cursor, StructurePropagation<KEY
structurePropagation.keyReplaceStrategy ); structurePropagation.keyReplaceStrategy );
} }
} }
}
}


if ( structurePropagation.hasRightKeyInsert ) private void overwriteKeyInternal( PageCursor cursor, StructurePropagation<KEY> structurePropagation, KEY newKey, int pos,
{ long stableGeneration, long unstableGeneration ) throws IOException
structurePropagation.hasRightKeyInsert = false; {
insertInInternal( cursor, structurePropagation, TreeNode.keyCount( cursor ), createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD,
structurePropagation.rightKey, structurePropagation.rightChild, stableGeneration, unstableGeneration );
stableGeneration, unstableGeneration ); int keyCount = TreeNode.keyCount( cursor );
} boolean couldOverwrite = bTreeNode.setKeyAtInternal( cursor, newKey, pos );
if ( !couldOverwrite )
{
// Remove key and right child
long rightChild = bTreeNode.childAt( cursor, pos + 1, stableGeneration, unstableGeneration );
bTreeNode.removeKeyAndRightChildAt( cursor, pos, keyCount );
TreeNode.setKeyCount( cursor, keyCount - 1 );

doInsertInInternal( cursor, structurePropagation, keyCount - 1, newKey, rightChild, stableGeneration, unstableGeneration );
} }
} }


Expand Down Expand Up @@ -862,10 +890,9 @@ private void replaceKeyByBubbleRightmostFromSubtree( PageCursor cursor,
if ( foundKeyBelow ) if ( foundKeyBelow )
{ {
// A key has been bubble up to us. // A key has been bubble up to us.
// It's in structurePropagation.leftKey and should be inserted in subtreePosition. // It's in structurePropagation.bubbleKey and should be inserted in subtreePosition.
createSuccessorIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, overwriteKeyInternal( cursor, structurePropagation, structurePropagation.bubbleKey, subtreePosition,
stableGeneration, unstableGeneration ); stableGeneration, unstableGeneration );
bTreeNode.setKeyAt( cursor, structurePropagation.bubbleKey, subtreePosition, INTERNAL );
} }
else else
{ {
Expand Down Expand Up @@ -1042,8 +1069,8 @@ private void underflowInLeaf( PageCursor cursor, StructurePropagation<KEY> struc
int keysToRebalance = bTreeNode.canRebalanceLeaves( leftSiblingCursor, leftSiblingKeyCount, cursor, keyCount ); int keysToRebalance = bTreeNode.canRebalanceLeaves( leftSiblingCursor, leftSiblingKeyCount, cursor, keyCount );
if ( keysToRebalance > 0 ) if ( keysToRebalance > 0 )
{ {
createSuccessorIfNeeded( leftSiblingCursor, structurePropagation, createSuccessorIfNeeded( leftSiblingCursor, structurePropagation, UPDATE_LEFT_CHILD,
StructurePropagation.UPDATE_LEFT_CHILD, stableGeneration, unstableGeneration ); stableGeneration, unstableGeneration );
rebalanceLeaf( leftSiblingCursor, leftSiblingKeyCount, cursor, keyCount, keysToRebalance , structurePropagation ); rebalanceLeaf( leftSiblingCursor, leftSiblingKeyCount, cursor, keyCount, keysToRebalance , structurePropagation );
} }
else if ( keysToRebalance == -1 ) else if ( keysToRebalance == -1 )
Expand Down
Expand Up @@ -237,7 +237,11 @@ abstract void insertKeyAndRightChildAt( PageCursor cursor, KEY key, long child,


abstract void removeKeyAndLeftChildAt( PageCursor cursor, int keyPos, int keyCount ); abstract void removeKeyAndLeftChildAt( PageCursor cursor, int keyPos, int keyCount );


abstract void setKeyAt( PageCursor cursor, KEY key, int pos, Type type ); /**
* Overwrite key at position with given key.
* @return True if key was overwritten, false otherwise.
*/
abstract boolean setKeyAtInternal( PageCursor cursor, KEY key, int pos );


abstract VALUE valueAt( PageCursor cursor, VALUE value, int pos ); abstract VALUE valueAt( PageCursor cursor, VALUE value, int pos );


Expand Down Expand Up @@ -288,7 +292,7 @@ static void goTo( PageCursor cursor, String messageOnError, long nodeId )
* Will internal overflow if inserting new key? * Will internal overflow if inserting new key?
* @return true if leaf will overflow, else false. * @return true if leaf will overflow, else false.
*/ */
abstract boolean internalOverflow( PageCursor cursor, int currentKeyCount, KEY newKey ); abstract Overflow internalOverflow( PageCursor cursor, int currentKeyCount, KEY newKey );


/** /**
* Will leaf overflow if inserting new key and value? * Will leaf overflow if inserting new key and value?
Expand All @@ -297,10 +301,15 @@ static void goTo( PageCursor cursor, String messageOnError, long nodeId )
abstract Overflow leafOverflow( PageCursor cursor, int currentKeyCount, KEY newKey, VALUE newValue ); abstract Overflow leafOverflow( PageCursor cursor, int currentKeyCount, KEY newKey, VALUE newValue );


/** /**
* Clean page from garbage to make room for further insert without having to split. * Clean page with leaf node from garbage to make room for further insert without having to split.
*/ */
abstract void defragmentLeaf( PageCursor cursor ); abstract void defragmentLeaf( PageCursor cursor );


/**
* Clean page with internal node from garbage to make room for further insert without having to split.
*/
abstract void defragmentInternal( PageCursor cursor );

abstract boolean leafUnderflow( PageCursor cursor, int keyCount ); abstract boolean leafUnderflow( PageCursor cursor, int keyCount );


/** /**
Expand Down
Expand Up @@ -186,8 +186,15 @@ void removeKeyAndRightChildAt( PageCursor cursor, int keyPos, int keyCount )
{ {
// Kill actual key // Kill actual key
placeCursorAtActualKey( cursor, keyPos, INTERNAL ); placeCursorAtActualKey( cursor, keyPos, INTERNAL );
int keyOffset = cursor.getOffset();
int keySize = readKeySize( cursor );
cursor.setOffset( keyOffset );
putTombstone( cursor ); putTombstone( cursor );


// Update dead space
int deadSpace = getDeadSpace( cursor );
setDeadSpace( cursor, deadSpace + keySize + bytesKeySize() );

// Remove for offsetArray // Remove for offsetArray
removeSlotAt( cursor, keyPos, keyCount, keyPosOffsetInternal( 0 ), keyChildSize() ); removeSlotAt( cursor, keyPos, keyCount, keyPosOffsetInternal( 0 ), keyChildSize() );
} }
Expand All @@ -197,8 +204,15 @@ void removeKeyAndLeftChildAt( PageCursor cursor, int keyPos, int keyCount )
{ {
// Kill actual key // Kill actual key
placeCursorAtActualKey( cursor, keyPos, INTERNAL ); placeCursorAtActualKey( cursor, keyPos, INTERNAL );
int keyOffset = cursor.getOffset();
int keySize = readKeySize( cursor );
cursor.setOffset( keyOffset );
putTombstone( cursor ); putTombstone( cursor );


// Update dead space
int deadSpace = getDeadSpace( cursor );
setDeadSpace( cursor, deadSpace + keySize + bytesKeySize() );

// Remove for offsetArray // Remove for offsetArray
removeSlotAt( cursor, keyPos, keyCount, keyPosOffsetInternal( 0 ) - childSize(), keyChildSize() ); removeSlotAt( cursor, keyPos, keyCount, keyPosOffsetInternal( 0 ) - childSize(), keyChildSize() );


Expand All @@ -207,9 +221,24 @@ void removeKeyAndLeftChildAt( PageCursor cursor, int keyPos, int keyCount )
} }


@Override @Override
void setKeyAt( PageCursor cursor, KEY key, int pos, Type type ) boolean setKeyAtInternal( PageCursor cursor, KEY key, int pos )
{ {
throw new UnsupportedOperationException( "Implement me" ); placeCursorAtActualKey( cursor, pos, INTERNAL );

int oldKeySize = readKeySize( cursor );
if ( oldKeySize > keyValueSizeCap )
{
cursor.setCursorException( format( "Read unreliable key size greater than cap: keySize=%d, keyValueSizeCap=%d",
oldKeySize, keyValueSizeCap ) );
}
int newKeySize = layout.keySize( key );
if ( newKeySize == oldKeySize )
{
// Fine, we can just overwrite
layout.writeKey( cursor, key );
return true;
}
return false;
} }


@Override @Override
Expand All @@ -220,10 +249,11 @@ VALUE valueAt( PageCursor cursor, VALUE into, int pos )
// Read value // Read value
int keySize = readKeySize( cursor ); int keySize = readKeySize( cursor );
int valueSize = readValueSize( cursor ); int valueSize = readValueSize( cursor );
if ( valueSize > keyValueSizeCap ) if ( keySize + valueSize > keyValueSizeCap )
{ {
cursor.setCursorException( format( "Read unreliable key, value size greater than cap: keySize=%d, keyValueSizeCap=%d", cursor.setCursorException(
valueSize, keyValueSizeCap ) ); format( "Read unreliable key, value size greater than cap: keySize=%d, valueSize=%d, keyValueSizeCap=%d",
keySize, valueSize, keyValueSizeCap ) );
} }
progressCursor( cursor, keySize ); progressCursor( cursor, keySize );
layout.readValue( cursor, into, valueSize ); layout.readValue( cursor, into, valueSize );
Expand All @@ -235,7 +265,7 @@ boolean setValueAt( PageCursor cursor, VALUE value, int pos )
{ {
placeCursorAtActualKey( cursor, pos, LEAF ); placeCursorAtActualKey( cursor, pos, LEAF );


int keySize = DynamicSizeUtil.readKeyOffset( cursor ); int keySize = DynamicSizeUtil.readKeySize( cursor );
int oldValueSize = DynamicSizeUtil.readValueSize( cursor ); int oldValueSize = DynamicSizeUtil.readValueSize( cursor );
int newValueSize = layout.valueSize( value ); int newValueSize = layout.valueSize( value );
if ( oldValueSize == newValueSize ) if ( oldValueSize == newValueSize )
Expand Down Expand Up @@ -293,13 +323,18 @@ int childOffset( int pos )
} }


@Override @Override
boolean internalOverflow( PageCursor cursor, int currentKeyCount, KEY newKey ) Overflow internalOverflow( PageCursor cursor, int currentKeyCount, KEY newKey )
{ {
// How much space do we have? // How much space do we have?
int allocSpace = getAllocSpace( cursor, currentKeyCount, INTERNAL ); int allocSpace = getAllocSpace( cursor, currentKeyCount, INTERNAL );
int deadSpace = getDeadSpace( cursor );

// How much space do we need?
int neededSpace = totalSpaceOfKeyChild( newKey ); int neededSpace = totalSpaceOfKeyChild( newKey );


return neededSpace > allocSpace; // There is your answer!
return neededSpace < allocSpace ? Overflow.NO :
neededSpace < allocSpace + deadSpace ? Overflow.NEED_DEFRAG : Overflow.YES;
} }


@Override @Override
Expand All @@ -310,10 +345,7 @@ Overflow leafOverflow( PageCursor cursor, int currentKeyCount, KEY newKey, VALUE
int allocSpace = getAllocSpace( cursor, currentKeyCount, LEAF ); int allocSpace = getAllocSpace( cursor, currentKeyCount, LEAF );


// How much space do we need? // How much space do we need?
int keySize = layout.keySize( newKey ); int neededSpace = totalSpaceOfKeyValue( newKey, newValue );
int valueSize = layout.valueSize( newValue );
int totalOverhead = bytesKeyOffset() + bytesKeySize() + bytesValueSize();
int neededSpace = keySize + valueSize + totalOverhead;


// There is your answer! // There is your answer!
return neededSpace < allocSpace ? Overflow.NO : return neededSpace < allocSpace ? Overflow.NO :
Expand All @@ -326,7 +358,8 @@ void defragmentLeaf( PageCursor cursor )
doDefragment( cursor, LEAF ); doDefragment( cursor, LEAF );
} }


private void defragmentInternal( PageCursor cursor ) @Override
void defragmentInternal( PageCursor cursor )
{ {
doDefragment( cursor, INTERNAL ); doDefragment( cursor, INTERNAL );
} }
Expand Down

0 comments on commit 579b58c

Please sign in to comment.