blob: f69cab42acbdbe68549757e411a6153b404221c2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.mavibot.btree;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException;
import org.apache.directory.mavibot.btree.exception.InvalidBTreeException;
import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
import org.apache.directory.mavibot.btree.serializer.LongArraySerializer;
import org.apache.directory.mavibot.btree.serializer.LongSerializer;
import org.apache.directory.mavibot.btree.util.Strings;
/**
* A class to examine a Mavibot database file.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class MavibotInspector
{
// The file to be read
private File dbFile;
// The recordManager
private static RecordManager rm;
private BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
// The name of the two page arrays for the global file and teh free pages
private static final String GLOBAL_PAGES_NAME = "__global__";
private static final String FREE_PAGES_NAME = "__free-pages__";
// The set of page array we already know about
private static Set<String> knownPagesArrays = new HashSet<String>();
// Create an array of pages to be checked for each B-tree, plus
// two others for the free pages and the global one
// We use one bit per page. It's 0 when the page
// hasn't been checked, 1 otherwise.
private static Map<String, int[]> checkedPages = new HashMap<String, int[]>();
static
{
knownPagesArrays.add( GLOBAL_PAGES_NAME );
knownPagesArrays.add( FREE_PAGES_NAME );
knownPagesArrays.add( BTreeConstants.BTREE_OF_BTREES_NAME );
knownPagesArrays.add( BTreeConstants.COPIED_PAGE_BTREE_NAME );
}
/**
* A private class to store a few informations about a btree
*
private static BtreeInfo btreeInfo;
static
{
btreeInfo = new BtreeInfo();
}
/**
* Create an instance of MavibotInspector
* @param dbFile The file to read
*/
public MavibotInspector( File dbFile )
{
this.dbFile = dbFile;
}
/**
* Check that the file exists
*/
private boolean checkFilePresence()
{
if ( dbFile == null )
{
System.out.println( "No mavibot database file was given" );
return false;
}
if ( !dbFile.exists() )
{
System.out.println( "Given mavibot database file " + dbFile + " doesn't exist" );
return false;
}
return true;
}
/**
* Pretty print the file size
*/
public void printFileSize() throws IOException
{
FileChannel fileChannel = new RandomAccessFile( dbFile, "r" ).getChannel();
long l = fileChannel.size();
fileChannel.close();
String msg;
if ( l < 1024 )
{
msg = l + " bytes";
}
else
{
msg = ( l / 1024 ) + " KB";
}
System.out.println( msg );
fileChannel.close();
}
/**
* Print the number of B-trees
*/
public void printNumberOfBTrees( RecordManagerHeader recordManagerHeader )
{
int nbBtrees = 0;
if ( rm != null )
{
nbBtrees = rm.getNbManagedTrees( recordManagerHeader );
}
// The number of trees. It must be at least 2 and > 0
System.out.println( "Total Number of BTrees: " + nbBtrees );
}
/**
* Print the B-tree's name
*/
public void printBTreeNames()
{
if ( rm == null )
{
System.out.println( "Couldn't find the number of managed btrees" );
return;
}
Set<String> trees = rm.getManagedTrees();
System.out.println( "\nManaged BTrees:" );
for ( String tree : trees )
{
System.out.println( tree );
}
System.out.println();
}
/**
* Check a B-tree
*/
public void inspectBTree( RecordManager recordManager ) throws IOException
{
if ( recordManager == null )
{
System.out.println( "Cannot check BTree(s)" );
return;
}
System.out.print( "BTree Name: " );
String name = readLine();
Transaction transaction = null;
try
{
transaction = rm.beginReadTransaction();
BTree<?, ?> pb = ( BTree<?, ?> ) rm.getBtree( transaction, name, Long.MAX_VALUE - 1 );
if ( pb == null )
{
System.out.println( "No BTree exists with the name '" + name + "'" );
return;
}
System.out.println( "\nBTree offset: " + String.format( "0x%1$08x", pb.getBtreeOffset() ) );
System.out.println( "BTree _info_ offset: " + String.format( "0x%1$08x", pb.getBtreeInfoOffset() ) );
System.out.println( "BTree root page offset: " + String.format( "0x%1$08x", pb.getRootPageOffset() ) );
System.out.println( "Number of elements present: " + pb.getNbElems() );
System.out.println( "BTree Page size: " + pb.getNbElems() );
System.out.println( "BTree revision: " + pb.getBtreeHeader().getRevision() );
System.out.println( "Key serializer: " + pb.getKeySerializerFQCN() );
System.out.println( "Value serializer: " + pb.getValueSerializerFQCN() );
System.out.println();
transaction.commit();
}
catch ( IOException ioe )
{
if ( transaction != null )
{
transaction.abort();
}
}
}
/**
* Load the full fie into a new RecordManager
*/
private boolean loadRm()
{
try
{
if ( rm != null )
{
System.out.println( "Closing record manager" );
rm.close();
}
rm = new RecordManager( dbFile.getAbsolutePath() );
System.out.println( "Loaded record manager" );
}
catch ( Exception e )
{
System.out.println( "Given database file seems to be corrupted. " + e.getMessage() );
return false;
}
return true;
}
/**
* Check the whole file.
*/
/* no qualifier */static void check( RecordManager recordManager )
{
RecordManagerHeader recordManagerHeader = recordManager.getCurrentRecordManagerHeader();
try
{
// Dump the infos
dumpInfos( recordManager, recordManagerHeader );
rm = recordManager;
// First check the RMheader
ByteBuffer recordManagerHeaderBuffer = ByteBuffer.allocate( RecordManager.recordManagerHeaderSize );
long fileSize = recordManager.fileChannel.size();
if ( fileSize < RecordManager.recordManagerHeaderSize )
{
throw new InvalidBTreeException( "File size too small : " + fileSize );
}
// Read the RMHeader
recordManager.fileChannel.read( recordManagerHeaderBuffer, 0L );
recordManagerHeaderBuffer.flip();
// The page size. It must be a power of 2, and above 16.
int pageSize = recordManagerHeaderBuffer.getInt();
if ( ( pageSize != recordManager.getPageSize( recordManagerHeader ) ) || ( pageSize < 32 )
|| ( ( pageSize & ( ~pageSize + 1 ) ) != pageSize ) )
{
throw new InvalidBTreeException( "Wrong page size : " + pageSize );
}
// Compute the number of pages in this file
long nbPages = ( fileSize - RecordManager.recordManagerHeaderSize ) / pageSize;
// The number of trees. It must be at least >= 2
int nbBtrees = recordManagerHeaderBuffer.getInt();
if ( ( nbBtrees < 0 ) || ( nbBtrees != recordManager.getNbManagedTrees( recordManagerHeader ) ) )
{
throw new InvalidBTreeException( "Wrong nb trees : " + nbBtrees );
}
// The current revision
long revision = recordManagerHeaderBuffer.getLong();
if ( revision != recordManagerHeader.getRevision() )
{
throw new InvalidBTreeException( "Wrong revision number : " + revision );
}
// The first free page offset. It must be either -1 or below file size
// and its value must be a modulo of pageSize
long firstFreePage = recordManagerHeaderBuffer.getLong();
if ( firstFreePage != BTreeConstants.NO_PAGE )
{
checkOffset( recordManager, recordManagerHeader, firstFreePage );
}
int nbPageBits = ( int ) ( nbPages / 32 );
// The global page array
checkedPages.put( GLOBAL_PAGES_NAME, new int[nbPageBits + 1] );
// The freePages array
checkedPages.put( FREE_PAGES_NAME, new int[nbPageBits + 1] );
// The B-tree of B-trees array
checkedPages.put( BTreeConstants.BTREE_OF_BTREES_NAME, new int[nbPageBits + 1] );
// Last, the Copied Pages B-tree array
checkedPages.put( BTreeConstants.COPIED_PAGE_BTREE_NAME, new int[nbPageBits + 1] );
// Check the free files
checkFreePages( recordManager, recordManagerHeader, checkedPages );
// The B-trees offsets
long currentBtreeOfBtreesOffset = recordManagerHeaderBuffer.getLong();
long currentCopiedPagesBtreeOffset = recordManagerHeaderBuffer.getLong();
// Check that the current LOB offset is valid
checkOffset( recordManager, recordManagerHeader, currentBtreeOfBtreesOffset );
// Check that the current CPB offset is valid
checkOffset( recordManager, recordManagerHeader, currentCopiedPagesBtreeOffset );
// Now, check the BTree of Btrees
checkListOfBtrees( recordManager, recordManagerHeader, checkedPages );
// And the Copied Pages BTree
checkBtree( recordManager, recordManagerHeader, currentCopiedPagesBtreeOffset, checkedPages );
// We can now dump the checked pages
//dumpCheckedPages( recordManager, recordManagerHeader, checkedPages );
}
catch ( Exception e )
{
// We catch the exception and rethrow it immediately to be able to
// put a breakpoint here
e.printStackTrace();
throw new InvalidBTreeException( "Error : " + e.getMessage() );
}
}
/**
* Dump the Mavibot file infos :
* <ul>
* <li>The recordManager header</li>
* <li><The copiedPages B-tree/li>
* <li>The B-tree of B-trees</li>
* </ul>
* @param recordManager The recordMnaager instance
* @param recordManagerHeader The current RecordManagerHeader
* @throws Exception
*/
public static void dumpInfos( RecordManager recordManager, RecordManagerHeader recordManagerHeader ) throws Exception
{
StringBuilder sb = new StringBuilder();
long nbPages = recordManagerHeader.lastOffset/recordManagerHeader.pageSize;
String[] pages = new String[(int)nbPages];
// RMH
sb.append( String.format( "0x%04X", 0L ) ).append( " RMH : " );
sb.append( "<LOB h[" ).append( recordManagerHeader.getRevision() ).append( "] : " ).append( String.format( "0x%04X",recordManagerHeader.currentListOfBtreesOffset ) );
sb.append( ">, " );
sb.append( "<CPB h[" ).append( recordManagerHeader.getRevision() ).append( "] : " ).append( String.format( "0x%04X",recordManagerHeader.currentCopiedPagesBtreeOffset ) );
sb.append( '>' );
pages[0] = sb.toString();
// The CPB
dumpCopiedPagesBtree( recordManager, recordManagerHeader, pages );
// The LOB
dumpListOfBtrees( recordManager, recordManagerHeader, pages );
boolean previousEmpty = false;
for ( String page : pages )
{
if ( page == null )
{
if ( !previousEmpty )
{
previousEmpty = true;
System.out.println();
}
System.out.print( "." );
}
else
{
System.out.println();
System.out.print( page );
previousEmpty = false;
}
}
System.out.println();
}
/**
* Check the List of Btrees
*/
private static void checkListOfBtrees( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages )
throws Exception
{
// Read the LOB header
PageIO[] bobHeaderPageIos = recordManager
.readPageIOs( recordManagerHeader.pageSize, recordManagerHeader.currentListOfBtreesOffset, Long.MAX_VALUE );
// update the checkedPages
updateCheckedPages( recordManagerHeader, checkedPages.get( BTreeConstants.BTREE_OF_BTREES_NAME ), recordManager.getPageSize( recordManagerHeader ),
bobHeaderPageIos );
updateCheckedPages( recordManagerHeader, checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.getPageSize( recordManagerHeader ), bobHeaderPageIos );
long dataPos = 0L;
// The B-tree header page ID
recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The number of transactions
int nbTransactions = recordManager.readInt( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// The current RMH
// The number of managed B-trees
int nbBtrees = recordManager.readInt( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// The current revision
recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
for ( int i = 0; i < nbBtrees; i++ )
{
// The b-tree offset
long offset = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
checkOffset( recordManager, recordManagerHeader, offset );
checkBtree( recordManager, recordManagerHeader, offset, checkedPages );
}
// The pending transactions
for ( int i = 0; i < nbTransactions; i++ )
{
// The number of managed B-trees
nbBtrees = recordManager.readInt( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// The current revision
recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
for ( int j = 0; j < nbBtrees; j++ )
{
// The b-tree offset
long offset = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
checkOffset( recordManager, recordManagerHeader, offset );
checkBtree( recordManager, recordManagerHeader, offset, checkedPages );
}
}
// Check the elements in the btree itself
// We will read every single page
//checkBtreeOfBtreesPage( recordManager, recordManagerHeader, checkedPages, rootPageOffset );
}
/**
* Dump the List of Btrees
*/
private static void dumpListOfBtrees( RecordManager recordManager, RecordManagerHeader recordManagerHeader, String[] pages ) throws Exception
{
StringBuilder sb = new StringBuilder();
// Read the LOB header
PageIO[] bobHeaderPageIos = recordManager
.readPageIOs( recordManagerHeader.pageSize, recordManagerHeader.currentListOfBtreesOffset, Long.MAX_VALUE );
// Get the offset
long bobHeaderOffset = bobHeaderPageIos[0].getOffset();
long dataPos = 0L;
// The B-tree header page ID
long pageId = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The number of transactions
int nbTransactions = recordManager.readInt( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// The current RMH
// The number of B-trees
int nbBtrees = recordManager.readInt( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// The revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// Dump the header
sb.append( String.format( "0x%04X", bobHeaderOffset ) ).append( " LOB H[" ).append( revision ).append( "] : " );
for ( int i = 0; i < nbBtrees; i++ )
{
// The B-tree offset
long offset = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
sb.append( "<BH[" ).append( revision ).append( "] : " ).append( String.format( "0x%04X", offset ) ).append( ">, " );
dumpUserBtree( recordManager, recordManagerHeader, "aaa", offset, pages );
}
sb.append( '\n' );
nbTransactions --;
// The pending transactions
for ( int i = 0; i < nbTransactions; i++ )
{
// The current RMH
// The number of B-trees
nbBtrees = recordManager.readInt( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
revision = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
sb.append( String.format( "0x%04X", bobHeaderOffset ) ).append( " LOB H[" ).append( revision ).append( "] : " );
for ( int j = 0; j < nbBtrees; j++ )
{
// The B-tree offset
long offset = recordManager.readLong( recordManagerHeader.pageSize, bobHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
sb.append( "<BH[" ).append( revision ).append( "] : " ).append( String.format( "0x%04X", offset ) ).append( ">, " );
}
sb.append( '\n' );
}
System.out.println( sb.toString() );
}
/**
* Dump the Copied Pages B-tree
*/
private static void dumpCopiedPagesBtree( RecordManager recordManager, RecordManagerHeader recordManagerHeader, String[] pages ) throws EndOfFileExceededException, IOException
{
StringBuilder sb = new StringBuilder();
// Read the CPB header
PageIO[] cpbbHeaderPageIos = recordManager
.readPageIOs( recordManagerHeader.pageSize, recordManagerHeader.currentCopiedPagesBtreeOffset, Long.MAX_VALUE );
// Get the offset
long cpbHeaderOffset = cpbbHeaderPageIos[0].getOffset();
long dataPos = 0L;
// The B-tree page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, cpbbHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree current revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, cpbbHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The nb elems in the tree
recordManager.readLong( recordManagerHeader.pageSize, cpbbHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree rootPage offset
long rootPageOffset = recordManager.readLong( recordManagerHeader.pageSize, cpbbHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree info offset
long infoOffset = recordManager.readLong( recordManagerHeader.pageSize, cpbbHeaderPageIos, dataPos );
// Dump the header
sb.append( String.format( "0x%04X", cpbHeaderOffset ) ).append( " CPB H[" ).append( revision ).append( "] : " );
sb.append( "<RP[" ).append( revision ).append( "] : " ).append( String.format( "0x%04X", rootPageOffset ) ).append( ">, " );
sb.append( "<Info:" ).append( String.format( "0x%04X", infoOffset ) ).append( '>' );
pages[(int)cpbHeaderOffset/recordManagerHeader.pageSize] = sb.toString();
// Dump the info
dumpBtreeInfo( recordManager, recordManagerHeader, infoOffset, "CPB", pages );
// Dump the CPB content
dumpCopiedPagesBtreeContent( recordManager, recordManagerHeader, rootPageOffset, RevisionNameSerializer.INSTANCE, LongArraySerializer.INSTANCE, pages );
}
/**
* Dump a BTree info
*/
private static <K, V> BTreeInfo<K, V> dumpBtreeInfo( RecordManager recordManager, RecordManagerHeader recordManagerHeader, long offset, String btreeName, String[] pages )
throws EndOfFileExceededException, IOException
{
PageIO[] infoPageIos = recordManager .readPageIOs( recordManagerHeader.pageSize, offset, Long.MAX_VALUE );
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", offset ) ).append( ' ' ).append( btreeName ).append( " Info" );
BTreeInfo<K, V> btreeInfo = new BTreeInfo<>();
// The info page may contain more than one PageIO
int nbPage = 0;
for ( PageIO pageIO : infoPageIos )
{
if ( nbPage > 0 )
{
sb.append( '-' ).append( nbPage );
}
nbPage++;
int pos = (int)(pageIO.getOffset()/recordManagerHeader.pageSize);
if ( ( pages[pos] != null ) && pages[pos].contains( "---copied page at" ) )
{
pages[pos] = new StringBuilder().append( pages[pos] ).append( ' ' ).append( sb.toString() ).toString();
}
else
{
pages[pos] = sb.toString();
}
}
// The B-tree page size
int dataPos = 0;
btreeInfo.setPageNbElem( recordManager.readInt( recordManagerHeader.pageSize, infoPageIos, dataPos ) );
dataPos += BTreeConstants.INT_SIZE;
// The tree name
ByteBuffer btreeNameBytes = recordManager.readBytes( recordManagerHeader.pageSize, infoPageIos, dataPos );
dataPos += BTreeConstants.INT_SIZE + btreeNameBytes.limit();
btreeInfo.setName( Strings.utf8ToString( btreeNameBytes ) );
// The keySerializer FQCN
ByteBuffer keySerializerBytes = recordManager.readBytes( recordManagerHeader.pageSize, infoPageIos, dataPos );
if ( keySerializerBytes != null )
{
String keySerializerFqcn = Strings.utf8ToString( keySerializerBytes );
btreeInfo.setKeySerializerFQCN( keySerializerFqcn );
btreeInfo.setKeySerializer( ( ( ElementSerializer<K> ) getSerializer( keySerializerFqcn ) ) );
}
dataPos += BTreeConstants.INT_SIZE + keySerializerBytes.limit();
// The valueSerialier FQCN
ByteBuffer valueSerializerBytes = recordManager.readBytes( recordManagerHeader.pageSize, infoPageIos, dataPos );
if ( valueSerializerBytes != null )
{
String valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes );
btreeInfo.setValueSerializerFQCN( valueSerializerFqcn );
btreeInfo.setValueSerializer( ( ( ElementSerializer<V> ) getSerializer( valueSerializerFqcn ) ) );
}
return btreeInfo;
}
/**
* Dump a CPB content
*/
private static void dumpCopiedPagesBtreeContent( RecordManager recordManager, RecordManagerHeader recordManagerHeader, long rootPageOffset, RevisionNameSerializer keySerializer,
LongArraySerializer valueSerializer, String[] pages ) throws IOException
{
// Process the rootPage
PageIO[] pageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, rootPageOffset, Long.MAX_VALUE );
// Deserialize the page now
long position = 0L;
// The page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The number of elements in the page
int nbElems = recordManager.readInt( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.INT_SIZE;
if ( nbElems == 0 )
{
// No elems, we are done
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", rootPageOffset ) ).append( " CPB RP[" ).append( revision ).append( "] : {}" );
pages[(int)(rootPageOffset/recordManagerHeader.pageSize)] = sb.toString();
return;
}
// The size of the data containing the keys and values
// Reads the bytes containing all the keys and values, if we have some
// We read big blob of data into ByteBuffer, then we will process
// this ByteBuffer
ByteBuffer byteBuffer = recordManager.readBytes( recordManagerHeader.pageSize, pageIos, position );
// Now, deserialize the data block. If the number of elements
// is positive, it's a Leaf, otherwise it's a Node
// Note that only a leaf can have 0 elements, and it's the root page then.
if ( nbElems > 0 )
{
// It's a leaf, process it as we may have sub-btrees
dumpCopiedPagesBtreeLeaf( recordManager, recordManagerHeader, keySerializer, valueSerializer, nbElems, revision, byteBuffer, pageIos[0].getOffset(), pages );
// Update the attached PageIOS
if ( pageIos.length > 0 )
{
for ( int i = 1; i < pageIos.length; i++ )
{
PageIO pageIo = pageIos[i];
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageIo.getOffset() ) ).append( " CPB RP[" ).append( revision ).append( "]-" ).append( i );
pages[(int)(pageIo.getOffset()/recordManagerHeader.pageSize)] = sb.toString();
}
}
}
else
{
// It's a node, dump its content, and iterate on the children
dumpCopiedPagesBtreeNode( recordManager, recordManagerHeader, keySerializer, valueSerializer, -nbElems, revision, byteBuffer, pageIos[0].getOffset(), pages );
// Update the attached PageIOS
if ( pageIos.length > 0 )
{
for ( int i = 1; i < pageIos.length; i++ )
{
PageIO pageIo = pageIos[i];
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageIo.getOffset() ) ).append( " CPB RP[" ).append( revision ).append( "]-" ).append( i );
pages[(int)(pageIo.getOffset()/recordManagerHeader.pageSize)] = sb.toString();
}
}
}
}
/**
* Dump a LOB content
*/
private static void dumpBtreeOfBtreesContent( RecordManager recordManager, RecordManagerHeader recordManagerHeader, long rootPageOffset, NameRevisionSerializer keySerializer,
LongSerializer valueSerializer, String[] pages ) throws IOException
{
// Process the rootPage
PageIO[] pageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, rootPageOffset, Long.MAX_VALUE );
// Deserialize the page now
long position = 0L;
// The page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The number of elements in the page
int nbElems = recordManager.readInt( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.INT_SIZE;
if ( nbElems == 0 )
{
// No elems, we are done
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", rootPageOffset ) ).append( " LOB RP[" ).append( revision ).append( "] : {}" );
pages[(int)(rootPageOffset/recordManagerHeader.pageSize)] = sb.toString();
return;
}
// The size of the data containing the keys and values
// Reads the bytes containing all the keys and values, if we have some
// We read big blob of data into ByteBuffer, then we will process
// this ByteBuffer
ByteBuffer byteBuffer = recordManager.readBytes( recordManagerHeader.pageSize, pageIos, position );
// Now, deserialize the data block. If the number of elements
// is positive, it's a Leaf, otherwise it's a Node
// Note that only a leaf can have 0 elements, and it's the root page then.
if ( nbElems > 0 )
{
// It's a leaf, process it as we may have sub-btrees
dumpBtreeOfBtreesLeaf( recordManager, recordManagerHeader, keySerializer, valueSerializer, nbElems, revision, byteBuffer, pageIos[0].getOffset(), pages );
// Update the attached PageIOS
if ( pageIos.length > 0 )
{
for ( int i = 1; i < pageIos.length; i++ )
{
PageIO pageIo = pageIos[i];
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageIo.getOffset() ) ).append( " LOB RP[" ).append( revision ).append( "]-" ).append( i );
int pos = (int)(pageIo.getOffset()/recordManagerHeader.pageSize);
if ( pages[pos] != null )
{
pages[pos] = new StringBuilder().append( pages[pos] ).append( ' ' ).append( sb.toString() ).toString();
}
else
{
pages[pos] = sb.toString();
}
}
}
}
else
{
// It's a node, dump its content, and iterate on the children
dumpBtreeOfBtreesNode( recordManager, recordManagerHeader, keySerializer, valueSerializer, -nbElems, revision, byteBuffer, pageIos[0].getOffset(), pages );
// Update the attached PageIOS
if ( pageIos.length > 0 )
{
for ( int i = 1; i < pageIos.length; i++ )
{
PageIO pageIo = pageIos[i];
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageIo.getOffset() ) ).append( " LOB RP[" ).append( revision ).append( "]-" ).append( i );
pages[(int)(pageIo.getOffset()/recordManagerHeader.pageSize)] = sb.toString();
}
}
}
}
/**
* Dump a LOB Btree leaf.
*/
private static void dumpBtreeOfBtreesLeaf( RecordManager recordManager, RecordManagerHeader recordManagerHeader, NameRevisionSerializer keySerializer,
LongSerializer valueSerializer, int nbElems, long revision, ByteBuffer byteBuffer, long cpbOffset, String[] pages ) throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", cpbOffset ) ).append( " LOB RP[" ).append( revision ).append( "](l) : " );
// Read each key and value
for ( int i = 0; i < nbElems; i++ )
{
try
{
// just deserialize the keys and values
// The value
long btreeOffset = valueSerializer.deserialize( byteBuffer );
// the key
NameRevision key = keySerializer.deserialize( byteBuffer );;
String btreeName = key.getName();
if ( i > 0 )
{
sb.append( ", " );
}
sb.append( key ).append( "->" ).append( String.format( "0x%04X", btreeOffset ) );
// Dump the user btree
dumpUserBtree( recordManager, recordManagerHeader, btreeName, btreeOffset, pages );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The LOB leaf byte buffer is too short : " + bue.getMessage() );
}
}
pages[(int)(cpbOffset/recordManagerHeader.pageSize)] = sb.toString();
}
/**
* Dump a LOB Btree Node.
*/
private static void dumpBtreeOfBtreesNode( RecordManager recordManager, RecordManagerHeader recordManagerHeader, NameRevisionSerializer keySerializer,
LongSerializer valueSerializer, int nbElems, long revision, ByteBuffer byteBuffer, long cpbOffset, String[] pages ) throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", cpbOffset ) ).append( " LOB RP[" ).append( revision ).append( "](n) : " );
long[] children = new long[nbElems + 1];
int i = 0;
// Read each key and value
for ( ; i < nbElems; i++ )
{
try
{
// The offsets of the child
children[i] = LongSerializer.INSTANCE.deserialize( byteBuffer );
// the key
NameRevision key = keySerializer.deserialize( byteBuffer );
if ( i > 0 )
{
sb.append( " | " );
}
sb.append( String.format( "0x%04X", children[i] ) ).append( " | " ).append( key );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The LOB leaf byte buffer is too short : " + bue.getMessage() );
}
}
// The last child
children[i] = LongSerializer.INSTANCE.deserialize( byteBuffer );
sb.append( " | " ).append( String.format( "0x%04X", children[i] ) );
pages[(int)(cpbOffset/recordManagerHeader.pageSize)] = sb.toString();
// Now iterate on each children
for ( long child : children )
{
dumpBtreeOfBtreesContent( recordManager, recordManagerHeader, child, keySerializer, valueSerializer, pages );
}
}
/**
* Dump a user Btree
*/
private static <K, V> void dumpUserBtree( RecordManager recordManager, RecordManagerHeader recordManagerHeader, String btreeName, long headerOffset, String[] pages )
throws EndOfFileExceededException, IOException
{
StringBuilder sb = new StringBuilder();
// Read the CPB header
PageIO[] btreeOffsetPageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, headerOffset, Long.MAX_VALUE );
long dataPos = 0L;
// The B-tree header page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, btreeOffsetPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree current revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, btreeOffsetPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The nb elems in the tree
recordManager.readLong( recordManagerHeader.pageSize, btreeOffsetPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree rootPage offset
long rootPageOffset = recordManager.readLong( recordManagerHeader.pageSize, btreeOffsetPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree info offset
long infoOffset = recordManager.readLong( recordManagerHeader.pageSize, btreeOffsetPageIos, dataPos );
// Dump the header
sb.append( String.format( "0x%04X", headerOffset ) ).append( ' ' ).append( btreeName ).append( " H[" ).append( revision ).append( "] : " );
sb.append( "<RP[" ).append( revision ).append( "] : " ).append( String.format( "0x%04X", rootPageOffset ) ).append( ">, " );
sb.append( "<Info:" ).append( String.format( "0x%04X", infoOffset ) ).append( '>' );
int pos = (int)headerOffset/recordManagerHeader.pageSize;
if ( pages[pos] != null )
{
pages[pos] = new StringBuilder().append( pages[pos] ).append( ' ' ).append( sb.toString() ).toString();
}
else
{
pages[pos] = sb.toString();
}
// Dump the info
BTreeInfo<K,V> btreeInfo = dumpBtreeInfo( recordManager, recordManagerHeader, infoOffset, btreeName, pages );
// Dump the user B-tree content
dumpUserBtreeContent( recordManager, recordManagerHeader, btreeInfo, rootPageOffset, pages );
}
/**
* Dump a User B-tree content
*/
private static <K, V> void dumpUserBtreeContent( RecordManager recordManager, RecordManagerHeader recordManagerHeader, BTreeInfo<K, V> btreeInfo, long rootPageOffset,
String[] pages ) throws IOException
{
// Process the rootPage
PageIO[] pageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, rootPageOffset, Long.MAX_VALUE );
// Deserialize the page now
long position = 0L;
// The pageID
long id = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The number of elements in the page
int nbElems = recordManager.readInt( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.INT_SIZE;
if ( nbElems == 0 )
{
// No elems, we are done
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", rootPageOffset ) ).append( ' ' ).append( btreeInfo.getName() ).append( " RP[" ).append( revision ).append( "] : {}" );
pages[(int)(rootPageOffset/recordManagerHeader.pageSize)] = sb.toString();
return;
}
// The size of the data containing the keys and values
// Reads the bytes containing all the keys and values, if we have some
// We read big blob of data into ByteBuffer, then we will process
// this ByteBuffer
ByteBuffer byteBuffer = recordManager.readBytes( recordManagerHeader.pageSize, pageIos, position );
// Now, deserialize the data block. If the number of elements
// is positive, it's a Leaf, otherwise it's a Node
// Note that only a leaf can have 0 elements, and it's the root page then.
if ( nbElems > 0 )
{
// It's a leaf, process it as we may have sub-btrees
dumpUserBtreeLeaf( recordManager, recordManagerHeader, btreeInfo, nbElems, revision, byteBuffer, pageIos[0].getOffset(), pages );
// Update the attached PageIOS
if ( pageIos.length > 0 )
{
for ( int i = 1; i < pageIos.length; i++ )
{
PageIO pageIo = pageIos[i];
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageIo.getOffset() ) ).append( ' ' ).append( btreeInfo.getName() );
sb.append( " RP[" ).append( revision ).append( "]-" ).append( i );
int pos = (int)(pageIo.getOffset()/recordManagerHeader.pageSize);
if ( pages[pos] != null )
{
pages[pos] = new StringBuilder().append( pages[pos] ).append( ' ' ).append( sb.toString() ).toString();
}
else
{
pages[pos] = sb.toString();
}
}
}
}
else
{
// It's a node, dump its content, and iterate on the children
dumpUserBtreeNode( recordManager, recordManagerHeader, btreeInfo, -nbElems, revision, byteBuffer, pageIos[0].getOffset(), pages );
// Update the attached PageIOS
if ( pageIos.length > 0 )
{
for ( int i = 1; i < pageIos.length; i++ )
{
PageIO pageIo = pageIos[i];
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageIo.getOffset() ) ).append( ' ' ).append( btreeInfo.getName() );
sb.append( " RP[" ).append( revision ).append( "]-" ).append( i );
pages[(int)(pageIo.getOffset()/recordManagerHeader.pageSize)] = sb.toString();
}
}
}
}
/**
* Dump a User Btree leaf.
*/
private static <K, V> void dumpUserBtreeLeaf( RecordManager recordManager, RecordManagerHeader recordManagerHeader, BTreeInfo<K, V> btreeInfo,
int nbElems, long revision, ByteBuffer byteBuffer, long cpbOffset, String[] pages ) throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", cpbOffset ) ).append( ' ' ).append( btreeInfo.getName() ).append( " RP[" ).append( revision ).append( "](l) : " );
// Read each key and value
for ( int i = 0; i < nbElems; i++ )
{
try
{
// just deserialize the keys and values
// The value
V value = btreeInfo.getValueSerializer().deserialize( byteBuffer );
// the key
K key = btreeInfo.getKeySerializer().deserialize( byteBuffer );
if ( i > 0 )
{
sb.append( ", " );
}
sb.append( '<' ).append( key ).append( ", " ).append( value ).append( '>' );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The user leaf byte buffer is too short : " + bue.getMessage() );
}
}
pages[(int)(cpbOffset/recordManagerHeader.pageSize)] = sb.toString();
}
/**
* Dump a User Btree Node.
*/
private static <K, V> void dumpUserBtreeNode( RecordManager recordManager, RecordManagerHeader recordManagerHeader, BTreeInfo<K, V> btreeInfo,
int nbElems, long revision, ByteBuffer byteBuffer, long pageOffset, String[] pages ) throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", pageOffset ) ).append( ' ' ).append( btreeInfo.getName() ).append( " RP[" ).append( revision ).append( "](n) : " );
long[] children = new long[nbElems + 1];
int i = 0;
// Read each key and value
for ( ; i < nbElems; i++ )
{
try
{
// The offsets of the child
children[i] = LongSerializer.INSTANCE.deserialize( byteBuffer );
// the key
K key = btreeInfo.getKeySerializer().deserialize( byteBuffer );
if ( i > 0 )
{
sb.append( " | " );
}
sb.append( String.format( "0x%04X", children[i] ) ).append( " | " ).append( key );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The User leaf byte buffer is too short : " + bue.getMessage() );
}
}
// The last child
children[i] = LongSerializer.INSTANCE.deserialize( byteBuffer );
sb.append( " | " ).append( String.format( "0x%04X", children[i] ) );
pages[(int)(pageOffset/recordManagerHeader.pageSize)] = sb.toString();
// Now iterate on each children
for ( long child : children )
{
dumpUserBtreeContent( recordManager, recordManagerHeader, btreeInfo, child, pages );
}
System.out.println( sb.toString() );
}
/**
* Dump a CPB Btree leaf.
*/
private static void dumpCopiedPagesBtreeLeaf( RecordManager recordManager, RecordManagerHeader recordManagerHeader, RevisionNameSerializer keySerializer,
LongArraySerializer valueSerializer, int nbElems, long revision, ByteBuffer byteBuffer, long cpbOffset, String[] pages ) throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", cpbOffset ) ).append( " CPB RP[" ).append( revision ).append( "](l) : " );
// Read each key and value
for ( int i = 0; i < nbElems; i++ )
{
try
{
// just deserialize the keys and values
// The value
long[] value = valueSerializer.deserialize( byteBuffer );
// the key
RevisionName key = keySerializer.deserialize( byteBuffer );
if ( i > 0 )
{
sb.append( ", " );
}
sb.append( key ).append( "->{" );
boolean isFirst = true;
for ( long offset : value )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ", " );
}
sb.append( String.format( "0x%04X", offset ) );
// Process the copied pages offset now
StringBuilder sb1 = new StringBuilder();
sb1.append( String.format( "0x%04X", offset ) );
sb1.append( " ---copied page at r" ).append( key.getRevision() ).append( "---" );
pages[(int)(offset/recordManagerHeader.pageSize)] = sb1.toString();
}
sb.append( '}' );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The LOB leaf byte buffer is too short : " + bue.getMessage() );
}
}
pages[(int)(cpbOffset/recordManagerHeader.pageSize)] = sb.toString();
}
/**
* Dump a CPB Btree Node.
*/
private static void dumpCopiedPagesBtreeNode( RecordManager recordManager, RecordManagerHeader recordManagerHeader, RevisionNameSerializer keySerializer,
LongArraySerializer valueSerializer, int nbElems, long revision, ByteBuffer byteBuffer, long cpbOffset, String[] pages ) throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( String.format( "0x%04X", cpbOffset ) ).append( " CPB RP[" ).append( revision ).append( "](n) : " );
long[] children = new long[nbElems + 1];
int i = 0;
// Read each key and value
for ( ; i < nbElems; i++ )
{
try
{
// The offsets of the child
children[i] = LongSerializer.INSTANCE.deserialize( byteBuffer );
// the key
RevisionName key = keySerializer.deserialize( byteBuffer );
if ( i > 0 )
{
sb.append( " | " );
}
sb.append( String.format( "0x%04X", children[i] ) ).append( " | " ).append( key );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The CPB leaf byte buffer is too short : " + bue.getMessage() );
}
}
// The last child
children[i] = LongSerializer.INSTANCE.deserialize( byteBuffer );
sb.append( " | " ).append( String.format( "0x%04X", children[i] ) );
pages[(int)(cpbOffset/recordManagerHeader.pageSize)] = sb.toString();
// Now iterate on each children
for ( long child : children )
{
dumpCopiedPagesBtreeContent( recordManager, recordManagerHeader, child, keySerializer, valueSerializer, pages );
}
}
/**
* Check a user's B-tree
*/
private static <K, V> void checkBtree( RecordManager recordManager, RecordManagerHeader recordManagerHeader, long btreeOffset,
Map<String, int[]> checkedPages ) throws Exception
{
// Read the B-tree header
PageIO[] btreeHeaderPageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, btreeOffset, Long.MAX_VALUE );
long dataPos = 0L;
// The B-tree page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, btreeHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree current revision
long btreeRevision = recordManager.readLong( recordManagerHeader.pageSize, btreeHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The nb elems in the tree
recordManager.readLong( recordManagerHeader.pageSize, btreeHeaderPageIos, dataPos );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree rootPage offset
long rootPageOffset = recordManager.readLong( recordManagerHeader.pageSize, btreeHeaderPageIos, dataPos );
checkOffset( recordManager, recordManagerHeader, rootPageOffset );
dataPos += BTreeConstants.LONG_SIZE;
// The B-tree info offset
long btreeInfoOffset = recordManager.readLong( recordManagerHeader.pageSize, btreeHeaderPageIos, dataPos );
checkOffset( recordManager, recordManagerHeader, btreeInfoOffset );
BTreeInfo<K, V> btreeInfo = checkBtreeInfo( recordManager, recordManagerHeader, checkedPages, btreeInfoOffset, btreeRevision );
// Update the checked pages
updateCheckedPages( recordManagerHeader, checkedPages.get( btreeInfo.getName() ), recordManager.getPageSize( recordManagerHeader ), btreeHeaderPageIos );
updateCheckedPages( recordManagerHeader, checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.getPageSize( recordManagerHeader ), btreeHeaderPageIos );
// And now, process the rootPage
checkBtreePage( recordManager, recordManagerHeader, btreeInfo, checkedPages, rootPageOffset );
}
/**
* Check the Btree of Btrees rootPage
*/
private static <K, V> void checkBtreePage( RecordManager recordManager, RecordManagerHeader recordManagerHeader, BTreeInfo<K, V> btreeInfo,
Map<String, int[]> checkedPages, long pageOffset ) throws Exception
{
PageIO[] pageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, pageOffset, Long.MAX_VALUE );
// Update the checkedPages array
updateCheckedPages( recordManagerHeader, checkedPages.get( btreeInfo.getName() ), recordManager.getPageSize( recordManagerHeader ), pageIos );
updateCheckedPages( recordManagerHeader, checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.getPageSize( recordManagerHeader ), pageIos );
// Deserialize the page now
long position = 0L;
// The page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The number of elements in the page
int nbElems = recordManager.readInt( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.INT_SIZE;
// The size of the data containing the keys and values
// Reads the bytes containing all the keys and values, if we have some
// We read big blob of data into ByteBuffer, then we will process
// this ByteBuffer
ByteBuffer byteBuffer = recordManager.readBytes( recordManagerHeader.pageSize, pageIos, position );
// Now, deserialize the data block. If the number of elements
// is positive, it's a Leaf, otherwise it's a Node
// Note that only a leaf can have 0 elements, and it's the root page then.
if ( nbElems >= 0 )
{
// It's a leaf, process it as we may have sub-btrees
checkBtreeLeaf( recordManager, recordManagerHeader, btreeInfo, checkedPages, nbElems, revision, byteBuffer, pageIos );
}
else
{
// It's a node
long[] children = checkBtreeNode( recordManager, recordManagerHeader, btreeInfo, checkedPages, -nbElems, revision, byteBuffer,
pageIos );
for ( int pos = 0; pos <= -nbElems; pos++ )
{
// Recursively check the children
checkBtreePage( recordManager, recordManagerHeader, btreeInfo, checkedPages, children[pos] );
}
}
}
/**
* Check the Btree info page
* @throws ClassNotFoundException
*/
private static <K, V> BTreeInfo<K, V> checkBtreeInfo( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages,
long btreeInfoOffset, long btreeRevision ) throws IOException
{
BTreeInfo<K, V> btreeInfo = new BTreeInfo<>();
PageIO[] btreeInfoPagesIos = recordManager.readPageIOs( recordManagerHeader.pageSize, btreeInfoOffset, Long.MAX_VALUE );
long dataPos = 0L;
// The B-tree page size
recordManager.readInt( recordManagerHeader.pageSize, btreeInfoPagesIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// The tree name
ByteBuffer btreeNameBytes = recordManager.readBytes( recordManagerHeader.pageSize, btreeInfoPagesIos, dataPos );
dataPos += BTreeConstants.INT_SIZE + btreeNameBytes.limit();
String btreeName = Strings.utf8ToString( btreeNameBytes );
// The keySerializer FQCN
ByteBuffer keySerializerBytes = recordManager.readBytes( recordManagerHeader.pageSize, btreeInfoPagesIos, dataPos );
if ( keySerializerBytes != null )
{
String keySerializerFqcn = Strings.utf8ToString( keySerializerBytes );
btreeInfo.setKeySerializer( ( ( ElementSerializer<K> ) getSerializer( keySerializerFqcn ) ) );
}
dataPos += BTreeConstants.INT_SIZE + keySerializerBytes.limit();
// The valueSerialier FQCN
ByteBuffer valueSerializerBytes = recordManager.readBytes( recordManagerHeader.pageSize, btreeInfoPagesIos, dataPos );
if ( valueSerializerBytes != null )
{
String valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes );
btreeInfo.setValueSerializer( ( ( ElementSerializer<V> ) getSerializer( valueSerializerFqcn ) ) );
}
dataPos += BTreeConstants.INT_SIZE + valueSerializerBytes.limit();
// The B-tree allowDuplicates flag
recordManager.readInt( recordManagerHeader.pageSize, btreeInfoPagesIos, dataPos );
dataPos += BTreeConstants.INT_SIZE;
// update the checkedPages
if ( !BTreeConstants.COPIED_PAGE_BTREE_NAME.equals( btreeName )
&& !BTreeConstants.BTREE_OF_BTREES_NAME.equals( btreeName ) )
{
//btreeName = btreeName + "<" + btreeRevision + ">";
}
btreeInfo.setName( btreeName );
// Update the checkedPages
int[] checkedPagesArray = checkedPages.get( btreeName );
if ( checkedPagesArray == null )
{
// Add the new name in the checkedPage name if it's not already there
checkedPagesArray = createPageArray( recordManager, recordManagerHeader );
checkedPages.put( btreeName, checkedPagesArray );
}
updateCheckedPages( recordManagerHeader, checkedPagesArray, recordManager.getPageSize( recordManagerHeader ), btreeInfoPagesIos );
updateCheckedPages( recordManagerHeader, checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.getPageSize( recordManagerHeader ), btreeInfoPagesIos );
return btreeInfo;
}
/**
* Get back the serializer instance
*/
@SuppressWarnings("unchecked")
private static <T> ElementSerializer<T> getSerializer( String serializerFqcn )
{
try
{
Class<?> serializerClass = Class.forName( serializerFqcn );
ElementSerializer<T> serializer = null;
try
{
serializer = ( ElementSerializer<T> ) serializerClass.getDeclaredField( "INSTANCE" ).get( null );
}
catch ( NoSuchFieldException e )
{
// ignore
}
if ( serializer == null )
{
serializer = ( ElementSerializer<T> ) serializerClass.newInstance();
}
return serializer;
}
catch ( Exception e )
{
throw new InvalidBTreeException( "Error : " + e.getMessage() );
}
}
/**
* Check the Btree of Btrees rootPage
*/
private static <K, V> void checkBtreeOfBtreesPage( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages,
long pageOffset ) throws Exception
{
PageIO[] pageIos = recordManager.readPageIOs( recordManagerHeader.pageSize, pageOffset, Long.MAX_VALUE );
// Update the checkedPages array
updateCheckedPages( recordManagerHeader, checkedPages.get( BTreeConstants.BTREE_OF_BTREES_NAME ), recordManager.getPageSize( recordManagerHeader ), pageIos );
updateCheckedPages( recordManagerHeader, checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.getPageSize( recordManagerHeader ), pageIos );
// Deserialize the page now
long position = 0L;
// The page ID
long id = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The revision
long revision = recordManager.readLong( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.LONG_SIZE;
// The number of elements in the page
int nbElems = recordManager.readInt( recordManagerHeader.pageSize, pageIos, position );
position += BTreeConstants.INT_SIZE;
// The size of the data containing the keys and values
// Reads the bytes containing all the keys and values, if we have some
// We read big blob of data into ByteBuffer, then we will process
// this ByteBuffer
ByteBuffer byteBuffer = recordManager.readBytes( recordManagerHeader.pageSize, pageIos, position );
// Now, deserialize the data block. If the number of elements
// is positive, it's a Leaf, otherwise it's a Node
// Note that only a leaf can have 0 elements, and it's the root page then.
if ( nbElems >= 0 )
{
// It's a leaf, process it as we may have sub-btrees
checkBtreeOfBtreesLeaf( recordManager, recordManagerHeader, checkedPages, nbElems, revision, byteBuffer, pageIos );
}
else
{
// It's a node
long[] children = checkBtreeOfBtreesNode( recordManager, recordManagerHeader, checkedPages, -nbElems, revision, byteBuffer,
pageIos );
for ( int pos = 0; pos <= -nbElems; pos++ )
{
// Recursively check the children
checkBtreeOfBtreesPage( recordManager, recordManagerHeader, checkedPages, children[pos] );
}
}
}
/**
* Check a Btree of Btrees leaf. It contains <revision, name> -> offset.
*/
private static void checkBtreeOfBtreesLeaf( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages,
int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos ) throws Exception
{
// Read each key and value
for ( int i = 0; i < nbElems; i++ )
{
try
{
// This is a normal value, an offset
long btreeOffset = byteBuffer.getLong();
checkOffset( recordManager, recordManagerHeader , btreeOffset );
// Now, process the key
RevisionName revisionName = RevisionNameSerializer.INSTANCE.deserialize( byteBuffer );
// Add the new name in the checkedPage name if it's not already there
int[] btreePagesArray = createPageArray( recordManager, recordManagerHeader );
checkedPages.put( revisionName.getName(), btreePagesArray );
// Now, we can check the Btree we just found
checkBtree( recordManager, recordManagerHeader, btreeOffset, checkedPages );
//System.out.println( "read <" + btreeName + "," + btreeRevision + "> : 0x" + Long.toHexString( btreeOffset ) );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The LOB leaf byte buffer is too short : " + bue.getMessage() );
}
}
}
/**
* Check a Btree leaf.
*/
private static <K, V> void checkBtreeLeaf( RecordManager recordManager, RecordManagerHeader recordManagerHeader, BTreeInfo<K, V> btreeInfo,
Map<String, int[]> checkedPages, int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos )
throws Exception
{
// Read each key and value
for ( int i = 0; i < nbElems; i++ )
{
try
{
// just deserialize the keys and values
// The value
btreeInfo.getValueSerializer().deserialize( byteBuffer );
// the key
btreeInfo.getKeySerializer().deserialize( byteBuffer );
}
catch ( BufferUnderflowException bue )
{
bue.printStackTrace();
throw new InvalidBTreeException( "The LOB leaf byte buffer is too short : " + bue.getMessage() );
}
}
}
/**
* Check a Btree of Btrees Node
*/
private static <K, V> long[] checkBtreeOfBtreesNode( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages,
int nbElems, long revision,
ByteBuffer byteBuffer, PageIO[] pageIos ) throws IOException
{
long[] children = new long[nbElems + 1];
// Read each value
for ( int i = 0; i < nbElems; i++ )
{
// The offsets of the child
long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
checkOffset( recordManager, recordManagerHeader, firstOffset );
children[i] = firstOffset;
// Read the revision
byteBuffer.getLong();
// read the btreeName
int btreeNameLength = byteBuffer.getInt();
// Read the Btree name
byte[] bytes = new byte[btreeNameLength];
byteBuffer.get( bytes );
}
// And read the last child
// The offsets of the child
long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
checkOffset( recordManager, recordManagerHeader, firstOffset );
children[nbElems] = firstOffset;
// and read the last value, as it's a node
return children;
}
/**
* Check a Btree node.
*/
private static <K, V> long[] checkBtreeNode( RecordManager recordManager, RecordManagerHeader recordManagerHeader, BTreeInfo<K, V> btreeInfo,
Map<String, int[]> checkedPages, int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos )
throws Exception
{
long[] children = new long[nbElems + 1];
// Read each key and value
for ( int i = 0; i < nbElems; i++ )
{
try
{
// The offsets of the child
long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
checkOffset( recordManager, recordManagerHeader, firstOffset );
children[i] = firstOffset;
// Now, read the key
btreeInfo.getKeySerializer().deserialize( byteBuffer );
}
catch ( BufferUnderflowException bue )
{
throw new InvalidBTreeException( "The LOB leaf byte buffer is too short : " + bue.getMessage() );
}
}
// The last child
// The offsets of the child
long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
checkOffset( recordManager, recordManagerHeader, firstOffset );
children[nbElems] = firstOffset;
return children;
}
/**
* Create an array of bits for pages
*/
private static int[] createPageArray( RecordManager recordManager, RecordManagerHeader recordManagerHeader ) throws IOException
{
long fileSize = recordManager.fileChannel.size();
int pageSize = recordManager.getPageSize( recordManagerHeader );
long nbPages = ( fileSize - RecordManager.recordManagerHeaderSize ) / pageSize;
int nbPageBits = ( int ) ( nbPages / 32 );
return new int[nbPageBits + 1];
}
/**
* Update the array of seen pages.
*/
private static void updateCheckedPages( RecordManagerHeader recordManagerHeader, int[] checkedPages, int pageSize, PageIO... pageIos )
{
for ( PageIO pageIO : pageIos )
{
long offset = pageIO.getOffset();
setCheckedPage( rm, recordManagerHeader, checkedPages, offset );
}
}
/**
* Check the offset to be sure it's a valid one :
* <ul>
* <li>It's >= 0</li>
* <li>It's below the end of the file</li>
* <li>It's a multiple of the pageSize
* </ul>
*/
private static void checkOffset( RecordManager recordManager, RecordManagerHeader recordManagerHeader, long offset ) throws IOException
{
if ( ( offset == BTreeConstants.NO_PAGE ) ||
( ( ( offset - RecordManager.recordManagerHeaderSize ) % recordManager.getPageSize( recordManagerHeader ) ) != 0 ) ||
( offset > recordManager.fileChannel.size() ) )
{
throw new InvalidBTreeException( "Invalid Offset : " + offset );
}
}
/**
* Check the free pages
*/
private static void checkFreePages( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages )
throws IOException
{
if ( recordManagerHeader.firstFreePage == BTreeConstants.NO_PAGE )
{
return;
}
// Now, read all the free pages
long currentOffset = recordManagerHeader.firstFreePage;
long fileSize = recordManager.fileChannel.size();
while ( currentOffset != BTreeConstants.NO_PAGE )
{
if ( currentOffset > fileSize )
{
System.out.println( "Wrong free page offset, above file size : " + currentOffset );
return;
}
try
{
PageIO pageIo = recordManager.fetchPageIO( recordManagerHeader.pageSize, currentOffset );
if ( currentOffset != pageIo.getOffset() )
{
System.out.println( "PageIO offset is incorrect : " + currentOffset + "-"
+ pageIo.getOffset() );
return;
}
setCheckedPage( recordManager, recordManagerHeader, checkedPages.get( GLOBAL_PAGES_NAME ), currentOffset );
setCheckedPage( recordManager, recordManagerHeader, checkedPages.get( FREE_PAGES_NAME ), currentOffset );
long newOffset = pageIo.getNextPage();
currentOffset = newOffset;
}
catch ( IOException ioe )
{
throw new InvalidBTreeException( "Cannot fetch page at : " + currentOffset );
}
}
}
/**
* Update the CheckedPages array
*/
private static void setCheckedPage( RecordManager recordManager, RecordManagerHeader recordManagerHeader, int[] checkedPages, long offset )
{
int pageNumber = ( int ) offset / recordManager.getPageSize( recordManagerHeader );
int nbBitsPage = ( BTreeConstants.INT_SIZE << 3 );
long pageMask = checkedPages[pageNumber / nbBitsPage];
long mask = 1L << pageNumber % nbBitsPage;
if ( ( pageMask & mask ) != 0 )
{
//throw new InvalidBTreeException( "The page " + offset + " has already been referenced" );
}
pageMask |= mask;
checkedPages[pageNumber / nbBitsPage] |= pageMask;
}
/**
* Output the pages that has been seen ('1') and those which has not been seen ('0'). The '.' represent non-pages
* at the end of the file.
*/
private static void dumpCheckedPages( RecordManager recordManager, RecordManagerHeader recordManagerHeader, Map<String, int[]> checkedPages )
throws IOException
{
// First dump the global array
int[] globalArray = checkedPages.get( GLOBAL_PAGES_NAME );
String result = dumpPageArray( recordManager, recordManagerHeader, globalArray );
String dump = String.format( "%1$-40s : %2$s", GLOBAL_PAGES_NAME, result );
System.out.println( dump );
// The free pages array
int[] freePagesArray = checkedPages.get( FREE_PAGES_NAME );
result = dumpPageArray( recordManager, recordManagerHeader, freePagesArray );
dump = String.format( "%1$-40s : %2$s", FREE_PAGES_NAME, result );
System.out.println( dump );
// The B-tree of B-trees pages array
int[] btreeOfBtreesArray = checkedPages.get( BTreeConstants.BTREE_OF_BTREES_NAME );
result = dumpPageArray( recordManager, recordManagerHeader, btreeOfBtreesArray );
dump = String.format( "%1$-40s : %2$s", BTreeConstants.BTREE_OF_BTREES_NAME, result );
System.out.println( dump );
// The Copied page B-tree pages array
int[] copiedPagesArray = checkedPages.get( BTreeConstants.COPIED_PAGE_BTREE_NAME );
result = dumpPageArray( recordManager, recordManagerHeader, copiedPagesArray );
dump = String.format( "%1$-40s : %2$s", BTreeConstants.COPIED_PAGE_BTREE_NAME, result );
System.out.println( dump );
// And now, all the other btree arrays
for ( String btreeName : checkedPages.keySet() )
{
// Don't do the array we have already processed
if ( knownPagesArrays.contains( btreeName ) )
{
continue;
}
int[] btreePagesArray = checkedPages.get( btreeName );
result = dumpPageArray( recordManager, recordManagerHeader, btreePagesArray );
dump = String.format( "%1$-40s : %2$s", btreeName, result );
System.out.println( dump );
}
}
/**
* @see #getPageOffsets()
*/
public static List<Long> getFreePages( RecordManagerHeader recordManagerHeader ) throws IOException
{
return getPageOffsets( recordManagerHeader, FREE_PAGES_NAME );
}
/**
* @see #getPageOffsets()
*/
public static List<Long> getGlobalPages( RecordManagerHeader recordManagerHeader ) throws IOException
{
return getPageOffsets( recordManagerHeader, GLOBAL_PAGES_NAME );
}
/**
* Gives a list of offsets of pages from the page array associated wit the given name.
*
* This method should always be called after calling check() method.
*
* @return a list of offsets
* @throws IOException
*/
public static List<Long> getPageOffsets( RecordManagerHeader recordManagerHeader, String pageArrayName ) throws IOException
{
List<Long> lst = new ArrayList<>();
int[] fparry = checkedPages.get( pageArrayName );
long nbPagesChecked = 0; // the 0th page will always be of RM header
long fileSize = rm.fileChannel.size();
long nbPages = ( fileSize - RecordManager.recordManagerHeaderSize ) / rm.getPageSize( recordManagerHeader );
for ( int checkedPage : fparry )
{
for ( int j = 0; j < 32; j++ )
{
if ( nbPagesChecked > nbPages + 1 )
{
break;
}
else
{
int mask = ( checkedPage & ( 1 << j ) );
if ( mask != 0 )
{
lst.add( nbPagesChecked * rm.getPageSize( recordManagerHeader ));
}
}
nbPagesChecked++;
}
}
return lst;
}
/**
* Process a page array
*/
private static String dumpPageArray( RecordManager recordManager, RecordManagerHeader recordManagerHeader, int[] checkedPages ) throws IOException
{
StringBuilder sb = new StringBuilder();
int i = -1;
int nbPagesChecked = 0;
long fileSize = recordManager.fileChannel.size();
long nbPages = ( fileSize - RecordManager.recordManagerHeaderSize ) / recordManager.getPageSize( recordManagerHeader );
for ( int checkedPage : checkedPages )
{
i++;
sb.append( " [" ).append( i ).append( "] " );
for ( int j = 0; j < 32; j++ )
{
if ( nbPagesChecked >= nbPages + 1 )
{
sb.append( "." );
}
else
{
if ( ( checkedPage & ( 1 << j ) ) == 0 )
{
sb.append( "0" );
}
else
{
sb.append( "1" );
}
}
nbPagesChecked++;
}
}
return sb.toString();
}
/**
* The entry point method
*/
public void start() throws Exception
{
if ( !checkFilePresence() )
{
return;
}
if ( !loadRm() )
{
return;
}
boolean stop = false;
while ( !stop )
{
System.out.println( "Choose an option:" );
System.out.println( "n - Print Number of BTrees" );
System.out.println( "b - Print BTree Names" );
System.out.println( "i - Inspect BTree" );
System.out.println( "c - Check Free Pages" );
System.out.println( "s - Get database file size" );
System.out.println( "d - Dump RecordManager" );
System.out.println( "r - Reload RecordManager" );
System.out.println( "o - Read page at offset" );
System.out.println( "q - Quit" );
char c = readOption();
switch ( c )
{
case 'n':
printNumberOfBTrees( rm.getCurrentRecordManagerHeader() );
break;
case 'b':
printBTreeNames();
break;
case 'i':
inspectBTree( rm );
break;
case 'c':
long fileSize = rm.fileChannel.size();
long nbPages = fileSize / rm.getPageSize( rm.getCurrentRecordManagerHeader() );
int nbPageBits = ( int ) ( nbPages / BTreeConstants.INT_SIZE );
Map<String, int[]> checkedPages = new HashMap<String, int[]>( 2 );
// The global page array
checkedPages.put( GLOBAL_PAGES_NAME, new int[nbPageBits + 1] );
// The freePages array
checkedPages.put( FREE_PAGES_NAME, new int[nbPageBits + 1] );
checkFreePages( rm, rm.getCurrentRecordManagerHeader(), checkedPages );
break;
case 's':
printFileSize();
break;
case 'd':
check( rm );
break;
case 'r':
loadRm();
break;
case 'o':
readPageAt(rm.getCurrentRecordManagerHeader() );
break;
case 'q':
stop = true;
break;
default:
System.out.println( "Invalid option" );
//c = readOption( br );
break;
}
}
try
{
rm.close();
br.close();
}
catch ( Exception e )
{
//ignore
}
}
/**
* Read the user's interaction
*/
private String readLine()
{
try
{
String line = br.readLine();
if ( line != null )
{
return line.trim();
}
else
{
return "";
}
}
catch ( Exception e )
{
throw new RuntimeException( e );
}
}
/**
* Process the input and get back the selected choice
*/
private char readOption()
{
try
{
String s = br.readLine();
if ( ( s == null ) || ( s.length() == 0 ) )
{
return ' ';
}
return s.charAt( 0 );
}
catch ( Exception e )
{
throw new RuntimeException( e );
}
}
private void readPageAt( RecordManagerHeader recordManagerHeader ) throws IOException
{
System.out.println();
System.out.print( "Offset: " );
String s = readLine();
long offset = -1;
try
{
offset = Long.parseLong( s.trim() );
}
catch( Exception e )
{
offset = -1;
}
if( offset < 0 || offset > (rm.fileChannel.size() - BTreeConstants.DEFAULT_PAGE_SIZE) )
{
System.out.println( "Invalid offset " + s );
return;
}
PageIO io = rm.fetchPageIO( recordManagerHeader.pageSize, offset );
List<Long> ll = new ArrayList<Long>();
ll.add( offset );
do
{
long next = io.getNextPage();
ll.add( next );
if ( next == -1 )
{
break;
}
io = rm.fetchPageIO( recordManagerHeader.pageSize, next );
}
while( true );
int i = 0;
for ( ; i < ll.size() - 2; i++ )
{
System.out.print( ll.get( i ) + " --> ");
}
System.out.println( ll.get( i ) );
}
/**
* Main method
*/
public static void main( String[] args ) throws Exception
{
if ( args.length == 0 )
{
System.out.println( "Usage java MavibotInspector <db-file-path>" );
System.exit( 0 );
}
File f = new File( args[0] );
MavibotInspector mi = new MavibotInspector( f );
mi.start();
}
}