//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1991 - 2000.
//
//  File:   IDXTAB.CXX
//
//  Contents:   Index Manager
//
//  Classes:    CIndexTable
//
//  History:    22-Mar-91   BartoszM    Created.
//              12-Feb-92   AmyA        Hacked all methods for FAT.
//              01-Jul-93   BartoszM    Rewrote to use memory mapped file
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <cistore.hxx>
#include <idxtab.hxx>
#include <eventlog.hxx>
#include <imprsnat.hxx>

//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
CWriteIndexFile::CWriteIndexFile ( PRcovStorageObj & rcovObj ) :
    _rcovObj(rcovObj),
    _xact(rcovObj),
    _iter(_xact, sizeof(CIndexRecord)),
    _xactPtr(0)
{
}


//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
void CWriteIndexFile::BackUp()
{
    ciAssert(_xactPtr > 0);

    _xactPtr--;
    _iter.Seek( _xactPtr );
}


//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
BOOL CWriteIndexFile::ReadRecord ( CIndexRecord * pRecord )
{
    ULONG ulRecCnt = _rcovObj.GetHeader().GetCount(_rcovObj.GetHeader().GetBackup());

    if (_xactPtr >= ulRecCnt)
        return(FALSE);

    _iter.GetRec( pRecord );
    _xactPtr++;

    return(TRUE);
}


//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
void CWriteIndexFile::WriteRecord ( CIndexRecord* pRecord )
{
    _iter.SetRec( pRecord );
    _xactPtr++;

    ULONG ulRecCnt = _rcovObj.GetHeader().GetCount(_rcovObj.GetHeader().GetBackup());

    if (ulRecCnt < _xactPtr)
        _rcovObj.GetHeader().SetCount(_rcovObj.GetHeader().GetBackup(), _xactPtr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CWriteIndexFile::IncrMMergeSeqNum
//
//  Synopsis:   Increments the master merge sequence number in the header.
//
//  History:    3-21-97   srikants   Created
//
//----------------------------------------------------------------------------

void CWriteIndexFile::IncrMMergeSeqNum()
{

    CRcovStorageHdr & storageHdr =  _rcovObj.GetHeader();

    CRcovUserHdr usrHdr;
    CIndexTableUsrHdr * pIdxUsrHdr = (CIndexTableUsrHdr *) &usrHdr;

    storageHdr.GetUserHdr( storageHdr.GetPrimary(), usrHdr );
    ciDebugOut(( DEB_ITRACE, "Current MMerge Seq Num = %d \n",
                 pIdxUsrHdr->GetMMergeSeqNum() ));

    pIdxUsrHdr->IncrMMergeSeqNum();

    ciDebugOut(( DEB_ITRACE, "New MMerge Seq Num = %d \n",
                 pIdxUsrHdr->GetMMergeSeqNum() ));

    storageHdr.SetUserHdr( storageHdr.GetBackup(), usrHdr );
}


//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
CReadIndexFile::CReadIndexFile ( PRcovStorageObj & rcovObj ) :
    _rcovObj(rcovObj),
    _xact(rcovObj),
    _iter(_xact, sizeof(CIndexRecord)),
    _xactPtr(0)
{
}


//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
void CReadIndexFile::BackUp()
{
    ciAssert(_xactPtr > 0);

    _xactPtr--;
    _iter.Seek( _xactPtr );
}


//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
BOOL CReadIndexFile::ReadRecord ( CIndexRecord * pRecord )
{
    ULONG ulRecCnt = _rcovObj.GetHeader().GetCount(_rcovObj.GetHeader().GetPrimary());

    if (_xactPtr >= ulRecCnt)
        return(FALSE);

    _iter.GetRec( pRecord );
    _xactPtr++;

    return(TRUE);
}


//+---------------------------------------------------------------------------
//
//  Member:     CAddReplaceIndexRecord::CAddReplaceIndexRecord, public
//
//  Synopsis:   Rewinds the file pointer indFile, then reads in IndexRecords until
//              either a record with INDEXID iid is found or EOF is reached.
//
//  Notes:      Whether or not a record with INDEXID iid is found can be
//              determined by calling Found().
//
//  History:    16-Mar-92   AmyA           Created.
//
//----------------------------------------------------------------------------

CAddReplaceIndexRecord::CAddReplaceIndexRecord ( CWriteIndexFile & indFile,
                                         INDEXID iid )
                                         : _indFile( indFile )

{
    _indFile.Rewind();

    do
    {
        _found = _indFile.ReadRecord ( this );
    } while (_found && Iid() != iid);
}


//+---------------------------------------------------------------------------
//
//  Member:     CAddReplaceIndexRecord::Write()
//
//  Synopsis:   Writes the information from CIndexRecord out to the file--
//              either replacing the record that was found in the constructor
//              (if there was one found) or appending to the end of the file
//              indicated by _indFile.
//
//  History:    28-Feb-95   DwightKr       Created.
//
//----------------------------------------------------------------------------

inline void CAddReplaceIndexRecord::WriteRecord()
{
    if ( Found() )
    {
        _indFile.BackUp();
    }

    _indFile.WriteRecord( this );
}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::CIndexTable, public
//
//  Synopsis:   Constructor.
//
//  History:    28-Mar-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

CIndexTable::CIndexTable ( CiStorage& storage, CTransaction& xact )
: _storage(storage), _pRcovObj(0)
{
    _pRcovObj = _storage.QueryIdxTableObject();
    Win4Assert( 0 != _pRcovObj );
}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::~CIndexTable, public
//
//  Synopsis:   Destructor.
//
//  History:    28-May-92   BartoszM       Created.
//
//----------------------------------------------------------------------------

CIndexTable::~CIndexTable ()
{
    delete _pRcovObj;
}


//+---------------------------------------------------------------------------
//
//  Member:     CIndexTableIter::CIndexTableIter
//
//  Synopsis:   Constructor.
//
//  History:    28-May-92   BartoszM       Created.
//
//----------------------------------------------------------------------------

CIndexTabIter::CIndexTabIter ( CIndexTable& idxTable )
        : _idxTable(idxTable),
          _indFile( idxTable.GetIndexTableObj() )
{
}


//+---------------------------------------------------------------------------
//
//  Member:     CIndexTabIter::Begin, public
//
//  Synopsis:   Position cursor at the beginning of table
//
//  History:    28-Mar-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

BOOL CIndexTabIter::Begin ()
{
    CNextIndexRecord rec(_indFile);

    if (!rec.Found())
        return FALSE;

    _indFile.Rewind();

    return TRUE;
}


//+---------------------------------------------------------------------------
//
//  Member:     CIndexTabIter::NextRecord, public
//
//  Synopsis:   Called during startup. Reads next record
//
//  Arguments:  [indexRecord] -- record to be filled
//
//  History:    28-Mar-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

BOOL CIndexTabIter::NextRecord ( CIndexRecord& indexRecord )
{
    CNextIndexRecord rec(_indFile);

    if (!rec.Found())
        return(FALSE);

    if ( rec.VersionStamp() < _idxTable._storage.GetStorageVersion() )
    {
        Win4Assert ( !"Corrupt index table" );

        PStorage & storage = GetStorage();

        storage.ReportCorruptComponent ( L"IndexTable1" );

        THROW( CException ( STATUS_INTERNAL_DB_CORRUPTION ));
    }
    else if ( rec.VersionStamp() > _idxTable._storage.GetStorageVersion() )
    {
        ciAssert ( !"Unknown index format: upgrade index software" );

        PStorage & storage = GetStorage();

        storage.ReportCorruptComponent ( L"IndexTable2" );

        THROW( CException ( STATUS_INTERNAL_DB_CORRUPTION ));
    }

    indexRecord._objectId = rec.ObjectId();
    indexRecord._iid = rec.Iid();
    indexRecord._type = rec.Type();
    indexRecord._maxWorkId = rec.MaxWorkId();

    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTabIter::~CIndexTabIter, public
//
//  Synopsis:   Iteration finished
//
//  History:    28-Mar-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

CIndexTabIter::~CIndexTabIter()
{
}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::SwapIndexes, public
//
//  Synopsis:   Replaces old indexes with a new one after merge
//
//  Arguments:  [pIndexNew] -- new index
//              [cIndexOld] -- count of old indexes to be removed
//              [aIidOld] -- array of old index id's
//
//  Notes:      ResMan LOCKED
//
//  History:    02-May-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

void CIndexTable::SwapIndexes ( CShadowMergeSwapInfo & info )
{
    CIndexRecord & record = info._recNewIndex;

    ciDebugOut (( DEB_ITRACE, "IndexManager: Adding index %lx, maxWid %ld\n",
        record.Iid(), record.MaxWorkId() ));

    CWriteIndexFile indFile( GetIndexTableObj() );

    // Mark old indexes deleted

    for ( unsigned i = 0; i < info._cIndexOld; i++ )
    {
        CIndexId idFull = info._aIidOld[i];
        if ( idFull.IsPersistent())
        {
            CAddReplaceIndexRecord rec(indFile, info._aIidOld[i]);
            if (!rec.Found())                   
            {
                //
                //  We have a persistent index which was not found in the
                //  index list.  This must be an index corruption.
                //

                ciDebugOut(( DEB_ERROR, "Can't find index 0x%lx\n",
                        info._aIidOld[i] ));
                Win4Assert( !"Corrupt index table" );
                _storage.ReportCorruptComponent( L"IndexTable3" );

                THROW( CException ( CI_CORRUPT_DATABASE ));
            }
            rec.SetType(itZombie);                      // And one more....
            rec.WriteRecord();
        }
    }

    //
    // Add record for the new index.
    //

    if ( widInvalid != record.ObjectId() )
        AddRecord ( indFile,
                    record.Iid(),
                    record.Type(),
                    record.MaxWorkId(),
                    record.ObjectId() );

    //
    // Replace the old fresh test entry with the new fresh test entry.
    //
    DeleteObject( indFile, partidDefault, itFreshLog, info._widOldFreshLog );
    AddRecord( indFile, CIndexId( 0, partidDefault ), itFreshLog,
               0, info._widNewFreshLog );

    indFile.Commit();
}

//+---------------------------------------------------------------------------
//
//  Function:   SwapIndexes
//
//  Synopsis:   This method marks the old indexes as "zombie" in the index
//              table, deletes the MMLog, MMFreshLog and NewMasterIndex
//              entries. It then adds an entry making the NewMaster the
//              current master.
//
//              This is done as a single TRANSACTION - either the entire
//              step succeeds or the previous state is retained.
//
//  Effects:    All the indexes that participated in the master merge will
//              be deleted from the index list and the new master will be
//              made the only master index.
//
//  Arguments:  [partid]       --  Partition Id where the master merge just
//              completed.
//              [cIndexOld]    --  Count of the indexes in aIidOld.
//              [aIidOld]      --  Array of index ids to be marked zombie.
//              [recNewMaster] --  CIndexRecord for the new master index.
//              [widMMLog]     --  WorkId of the MMLog object.
//
//  History:    4-04-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
void CIndexTable::SwapIndexes ( CMasterMergeSwapInfo & info )
{
    CIndexRecord & recNewMaster = info._recNewIndex;
    CIndexRecord & recNewKeyList = info._recNewKeyList;

    ciDebugOut (( DEB_ITRACE, "Master Merge Completed\n"));

    CWriteIndexFile indFile( GetIndexTableObj() );

    //
    // Mark old indexes as "Zombie".
    //
    for ( unsigned i = 0; i < info._cIndexOld; i++ )
    {
        CIndexId idFull = info._aIidOld[i];
        Win4Assert( idFull.IsPersistent() );
        Win4Assert( idFull.PartId() == info._partid );

        CAddReplaceIndexRecord rec(indFile, info._aIidOld[i]);

        if (!rec.Found())
        {
            //
            //  We have a persistent index which was not found in the
            //  index list.  This must be an index corruption.
            //

            ciDebugOut(( DEB_ERROR, "Can't find index 0x%lx\n",
                    info._aIidOld[i] ));
            Win4Assert( !"Corrupt index table" );
            _storage.ReportCorruptComponent( L"IndexTable4" );

            THROW( CException ( CI_CORRUPT_DATABASE ));
        }
        rec.SetType(itZombie);                      // And one more....
        rec.WriteRecord();
    }

    //
    // Delete the MMLog entry.
    //
    DeleteObject( indFile, info._partid, itMMLog, info._widMMLog );

    //
    // Delete the itMMKeyList entry.
    //
    DeleteObject( indFile, partidKeyList, itMMKeyList,
                  recNewKeyList.ObjectId() );

    //
    // Delete the old itKeyList entry and add an entry for the new
    // key list.
    //
    {
        //
        // DeleteRecord assumes that the record is found. When we create
        // the key list for the first time, it may not be present.
        //
        CAddReplaceIndexRecord rec( indFile, info._iidOldKeyList );
        if ( rec.Found() )
        {
            rec.SetIid( CIndexId(iidInvalid,partidInvalid) );
            rec.WriteRecord();
        }
    }

    AddRecord( indFile, recNewKeyList.Iid(),
               recNewKeyList.Type(),
               recNewKeyList.MaxWorkId(),
               recNewKeyList.ObjectId()
             );
    //
    // Delete the entry for the new master index and make it the
    // current master index.
    //
    DeleteRecord( indFile, recNewMaster.Iid() );
    AddRecord (
        indFile,
        recNewMaster.Iid(),
        itMaster,       // Note the change from itNewMaster to itMaster
        recNewMaster.MaxWorkId(),
        recNewMaster.ObjectId());

    //
    // Replace the old fresh test entry with the new fresh test entry.
    //
    DeleteObject( indFile, partidDefault, itFreshLog, info._widOldFreshLog );
    AddRecord( indFile, CIndexId( 0, partidDefault ), itFreshLog, 0,
               info._widNewFreshLog );

    //
    // Increment the master merge sequence number.
    //
    indFile.IncrMMergeSeqNum();
    indFile.Commit();
}

PIndexTabIter* CIndexTable::QueryIterator()
{
    return new CIndexTabIter ( *this );
}

PStorage& CIndexTable::GetStorage()
{
    return(_storage);
}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::RemoveIndex, public
//
//  Synopsis:   Removes index from table
//
//  Arguments:  [iid] -- index id
//
//  Notes:      ResMan LOCKED
//
//  History:    02-May-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

#pragma optimize( "", off )
void CIndexTable::RemoveIndex ( INDEXID iid )
{
    CImpersonateSystem impersonate;

    CWriteIndexFile indFile( GetIndexTableObj() );

    DeleteRecord( indFile, iid );

    indFile.Commit();
}
#pragma optimize( "", on )


//+---------------------------------------------------------------------------
//
//  Function:   AddObject
//
//  Synopsis:   Appends a record for the specified object to the
//              index table.
//
//  Arguments:  [partid] -- Id of the partition to which the object
//              belongs.
//              [it]     -- Index type of the object.
//              [wid]    -- WorkId of the object.
//
//  History:    2-18-94   srikants   Created
//
//----------------------------------------------------------------------------

void CIndexTable::AddObject( PARTITIONID partid, IndexType it, WORKID wid )
{
    CWriteIndexFile indFile( GetIndexTableObj() );

    AddRecord ( indFile, CIndexId ( 0, partid ), it, 0, wid );

    indFile.Commit();
}

//+---------------------------------------------------------------------------
//
//  Function:   AddMMergeObjects
//
//  Synopsis:   This method adds records for the NewMaster Index,
//              and MasterMerge Log to the index table.
//
//  Arguments:  [partid]       --  Partition id of the partition in which
//              master merge is being done.
//              [recNewMaster] --  The index record for the new master index.
//              [widMMLog]     --  WorkId of the MasterMerge Log.
//              [deletedIndex] --  Index id for the current index
//
//  History:    4-04-94   srikants   Created
//
//  Notes:      The recNewMaster must be fully initialized with the correct
//              indexType, WorkId and MaxWorkId.
//
//----------------------------------------------------------------------------

void CIndexTable::AddMMergeObjects( PARTITIONID partid,
                           CIndexRecord & recNewMaster,
                           WORKID  widMMLog,
                           WORKID  widMMKeyList,
                           INDEXID iidDelOld,
                           INDEXID iidDelNew )
{

    CWriteIndexFile indFile( GetIndexTableObj() );

    Win4Assert( recNewMaster.Type() == itNewMaster );
    Win4Assert( iidDeleted1 == iidDelOld && iidDeleted2 == iidDelNew ||
                iidDeleted2 == iidDelOld && iidDeleted1 == iidDelNew );

#if CIDBG == 1
    CIndexId iid( recNewMaster.Iid() );
    Win4Assert( iid.PartId() == partid );
#endif  // CIDBG == 1

    AddRecord( indFile, recNewMaster.Iid(), itNewMaster,
               recNewMaster.MaxWorkId(), recNewMaster.ObjectId() );
    AddRecord( indFile, CIndexId( 0, partid ), itMMLog, 0,widMMLog );
    AddRecord( indFile, CIndexId( 0, partidKeyList ), itMMKeyList, 0, widMMKeyList );

    DeleteRecord( indFile, iidDelOld );
    AddRecord( indFile, iidDelNew, itDeleted, 0, 0 );

    indFile.Commit();
}


inline BOOL IsMatched( const CIndexRecord & rec,
                       INDEXID iid, IndexType it, WORKID wid )
{
    return rec.Type() == (ULONG) it && rec.Iid() == iid && rec.ObjectId() == wid ;
}

//+---------------------------------------------------------------------------
//
//  Function:   DeleteObject
//
//  Synopsis:   Deletes the record for the specified object by marking
//              it as "iidInvalid". The record will be deleted only
//              if there is an exact match with the partid, it and wid.
//
//  Arguments:  [partid] --  Partition Id.
//              [it]     --  Index type of the object.
//              [wid]    --  Work Id to match on.
//
//  History:    2-18-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
void CIndexTable::DeleteObject( PARTITIONID partid, IndexType it, WORKID wid )
{
    CWriteIndexFile indFile( GetIndexTableObj() );
    DeleteObject( indFile, partid, it, wid );
    indFile.Commit();
}

void CIndexTable::DeleteObject( CWriteIndexFile & indFile,
            PARTITIONID partid, IndexType it, WORKID wid )
{
    CIndexRecord    rec;
    BOOL found;
    indFile.Rewind();
    do
    {
        found = indFile.ReadRecord ( &rec );
    }
    while ( found && !IsMatched( rec, CIndexId(0, partid), it, wid) );

    if ( found ) {
        indFile.BackUp();
        rec._iid = (INDEXID) CIndexId(iidInvalid, partidInvalid);
        rec._objectId =  widInvalid;
        indFile.WriteRecord( &rec );
    }
}


//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::AddRecord, private
//
//  Synopsis:   Adds new record to table
//
//  Arguments:  [iid] -- index id
//              [type] -- type of record
//              [maxWorkId] -- max work id in the index
//              [objectId] -- id of the index object
//
//  Notes:      ResMan LOCKED
//
//  History:    02-May-91   BartoszM       Created.
//
//----------------------------------------------------------------------------

void CIndexTable::AddRecord( CWriteIndexFile & indFile,
                               INDEXID iid,
                               ULONG type,
                               WORKID maxWorkId,
                               WORKID objectId )
{
    ciDebugOut (( DEB_ITRACE, "Indexes: AddRecord %lx, %ld %s\n",
        iid, maxWorkId, (type == itMaster)? "master": "not-master" ));

    CAddReplaceIndexRecord rec(indFile, CIndexId(iidInvalid,partidInvalid) );

    rec.SetIid(iid);
    rec.SetType(type);
    rec.SetWid(maxWorkId);
    rec.SetObjectId ( objectId );
    rec.WriteRecord();
}



//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::AddIndex, public
//
//  Synopsis:   Adds new index to table
//
//  Arguments:  [iid] -- index id
//              [type] -- type of record
//              [maxWorkId] -- max work id in the index
//              [objectId] -- id of the index object
//
//  Notes:      ResMan LOCKED
//
//  History:    14-Jul-94   DwightKr       Created.
//
//----------------------------------------------------------------------------
void CIndexTable::AddIndex( INDEXID iid,
                            IndexType type,
                            WORKID maxWorkId,
                            WORKID objectId )
{
    CWriteIndexFile indFile( GetIndexTableObj() );
    AddRecord( indFile, iid, type, maxWorkId, objectId );
    indFile.Commit();
}


//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::LokEmpty, public
//
//  Synopsis:   Deleted everything from the index table
//
//  Notes:      ResMan LOCKED
//
//  History:    16-Aug-94   DwightKr    Created
//
//----------------------------------------------------------------------------
void CIndexTable::LokEmpty()
{

    PRcovStorageObj & rcovObj = GetIndexTableObj();
    CRcovStrmWriteTrans xact( rcovObj );

    rcovObj.GetHeader().SetCount( rcovObj.GetHeader().GetBackup(), 0 );
    xact.Empty();
    xact.Seek(0);

    xact.Commit();
}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::LokMakeBackupCopy
//
//  Synopsis:   Makes a backup copy of the index table.
//
//  Arguments:  [storage]         - Storage to use for creation of the
//              destination index table.
//              [fFullSave]       - Set to TRUE if a full save is being performed.
//              [progressTracker] - Progress tracker.
//
//  History:    3-18-97   srikants   Created
//
//----------------------------------------------------------------------------

void CIndexTable::LokMakeBackupCopy( PStorage & storage,
                                     BOOL fFullSave,
                                     PSaveProgressTracker & progressTracker )
{

    //
    // Create a new index table object using the storage provided.
    //
    PRcovStorageObj * pDstObj = storage.QueryIdxTableObject();
    XPtr<PRcovStorageObj> xDstObj( pDstObj );

    PRcovStorageObj & srcObj = GetIndexTableObj();

    //
    // Copy the contents of source to destination.
    //
    CCopyRcovObject copier( *pDstObj, srcObj );
    NTSTATUS status = copier.DoIt();

    if ( STATUS_SUCCESS != status )
    {
        THROW(CException(status) );
    }

    //
    // Set the Full/Partial Save bit appropriately.
    //
    CRcovStrmAppendTrans    xact( *pDstObj );

    CRcovStorageHdr & storageHdr = pDstObj->GetHeader();

    CRcovUserHdr usrHdr;
    CIndexTableUsrHdr * pIdxUsrHdr = (CIndexTableUsrHdr *) &usrHdr;

    storageHdr.GetUserHdr( storageHdr.GetBackup(), usrHdr );

    if ( fFullSave )
        pIdxUsrHdr->SetFullSave();
    else pIdxUsrHdr->ClearFullSave();

    storageHdr.SetUserHdr( storageHdr.GetBackup(), usrHdr );

    xact.Commit();

}

//+---------------------------------------------------------------------------
//
//  Member:     CIndexTable::GetUserHdrInfo
//
//  Synopsis:   Retrieves the information in the user header.
//
//  Arguments:  [mMergeSeqNum] - Master Merge sequence number.
//              [fFullSave]    - Set to TRUE if a full save was performed.
//
//  History:    3-21-97   srikants   Created
//
//----------------------------------------------------------------------------

void CIndexTable::GetUserHdrInfo( unsigned & mMergeSeqNum, BOOL & fFullSave )
{
    PRcovStorageObj & obj = GetIndexTableObj();

    CRcovUserHdr usrHdr;
    CIndexTableUsrHdr * pIdxUsrHdr = (CIndexTableUsrHdr *) &usrHdr;

    CRcovStorageHdr & storageHdr =  obj.GetHeader();

    storageHdr.GetUserHdr( storageHdr.GetPrimary(), usrHdr );
    ciDebugOut(( DEB_ERROR, "Current MMerge Seq Num = %d \n",
                 pIdxUsrHdr->GetMMergeSeqNum() ));

    mMergeSeqNum = pIdxUsrHdr->GetMMergeSeqNum();
    fFullSave = pIdxUsrHdr->IsFullSave();

}