//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1991 - 1998. // // File: PINDEX.CXX // // Contents: Persistent Index // // Classes: CPersIndex, CMergeSourceCursor // // History: 03-Apr-91 BartoszM Created stub. // 20-Apr-94 DwightKr Moved CMergeSourceCursor here // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include "pindex.hxx" #include "pcomp.hxx" #include "mcursor.hxx" #include "fresh.hxx" #include "physidx.hxx" #include "pcomp.hxx" #include "fretest.hxx" #include "indsnap.hxx" #include "keylist.hxx" #include "partn.hxx" unsigned const FOUR_MEGABYTES = 0x400000; //+--------------------------------------------------------------------------- // // Member: CPersIndex::Size, public // // Synopsis: Returns size in pages // // History: 22-May-92 BartoszM Created. // //---------------------------------------------------------------------------- unsigned CPersIndex::Size() const { return _xPhysIndex->PageSize(); } void CPersIndex::FillRecord ( CIndexRecord& record ) { record._objectId = ObjectId(); record._iid = GetId(); record._type = IsMaster()? itMaster: itShadow; record._maxWorkId = MaxWorkId(); } //+--------------------------------------------------------------------------- // // Member: CPersIndex::CPersIndex, public // // Synopsis: Create an empty index // // Arguments: [id] -- index id // [storage] -- physical storage // // History: 3-Apr-91 BartoszM Created. // //---------------------------------------------------------------------------- CPersIndex::CPersIndex( PStorage & storage, WORKID objectId, INDEXID iid, unsigned c4KPages, CDiskIndex::EDiskIndexType idxType ) : CDiskIndex( iid, idxType ), _sigPersIndex( eSigPersIndex ), _storage( storage ), _obj( storage.QueryObject( objectId ) ), _fAbortMerge( FALSE ) { XPtr sStream( storage.QueryNewIndexStream( _obj.GetObj(), CDiskIndex::eMaster == idxType ) );; _xPhysIndex.Set( new CPhysIndex( storage, _obj.GetObj(), objectId, c4KPages, sStream ) ); _xPhysIndex->SetPageGrowth( FOUR_MEGABYTES / CI_PAGE_SIZE ); Win4Assert( 0 == sStream.GetPointer() ); _xDir.Set( _storage.QueryNewDirectory( _obj.GetObj() ) ); } //+--------------------------------------------------------------------------- // // Member: CPersIndex::CPersIndex, public // // Synopsis: Restore an index from storage // // Arguments: [id] -- index id // [storage] -- physical storage // [widMax] -- max work id // [isMaster] -- Set to TRUE if this is a master index. // [fWritable] -- Set to TRUE if various streams should be // opened for Write access. // [fReadDir] -- should the directory be opened for r or r/w // // History: 3-Apr-91 BartoszM Created. // //---------------------------------------------------------------------------- CPersIndex::CPersIndex( PStorage & storage, WORKID objectId, INDEXID iid, WORKID widMax, CDiskIndex::EDiskIndexType idxType, PStorage::EOpenMode mode, BOOL fReadDir ) : CDiskIndex( iid, idxType, widMax ), _sigPersIndex( eSigPersIndex ), _storage( storage ), _obj( storage.QueryObject( objectId ) ), _fAbortMerge( FALSE ) { Win4Assert( PStorage::eOpenForWrite == mode || PStorage::eOpenForRead == mode ); PStorage::EOpenMode modeIndex = mode; // // Open master indexes writable so we can shrink them from the front // if ( CDiskIndex::eMaster == idxType ) modeIndex = PStorage::eOpenForWrite; PMmStream * pStream = storage.QueryExistingIndexStream( _obj.GetObj(), modeIndex ); XPtr sStream( pStream ); _xPhysIndex.Set( new CPhysIndex( storage, _obj.GetObj(), objectId, modeIndex, sStream ) ); Win4Assert( 0 == sStream.GetPointer() ); Win4Assert( fReadDir ); if ( fReadDir ) _xDir.Set( _storage.QueryExistingDirectory( _obj.GetObj(), mode ) ); else _xDir.Set( _storage.QueryNewDirectory( _obj.GetObj() ) ); } //+--------------------------------------------------------------------------- // // Member: CMergeSourceCursor::CMergeSourceCursor // // Synopsis: Constructor // // History: 29-Aug-92 BartoszM Created // //---------------------------------------------------------------------------- CMergeSourceCursor::CMergeSourceCursor ( CIndexSnapshot& indSnap, const CKeyBuf * pKey ) { if (0 != pKey) { CKey SplitKey(*pKey); _pCurSrc = indSnap.QueryMergeCursor ( &SplitKey ); } else { _pCurSrc = indSnap.QueryMergeCursor (); } } //+--------------------------------------------------------------------------- // // Member: CMergeSourceCursor::~CMergeSourceCursor // // Synopsis: Destructor // // History: 29-Aug-92 BartoszM Created // //---------------------------------------------------------------------------- CMergeSourceCursor::~CMergeSourceCursor () { delete _pCurSrc; } //+--------------------------------------------------------------------------- // // Member: CPersIndex::Merge, public // // Synopsis: Merge index(es) into an empty pesistent index. // // Effects: Fills the persistent index with data from the input // indexes. // [fresh] is deleted. // // Arguments: [indSnap] -- array of indexes to be merged // [pNewKeyList] -- Keylist to merge (master merge only) // [mergeProgress] -- % merge complete // // Requires: The index is initially empty. // // Notes: Every compressor is transacted. // // History: 15-May-91 KyleP Use new PutOccurrence() method. // 16-Apr-91 KyleP Created. // 17-Feb-93 KyleP Merge keylist // 25-Oct-95 DwightKr Add merge complete measurement // //---------------------------------------------------------------------------- void CPersIndex::Merge( CIndexSnapshot& indSnap, const CPartition & partn, CCiFrmPerfCounter & mergeProgress, BOOL fGetRW ) { // Calculate max of all input widMaxs #if CIDBG == 1 unsigned cKey = 0; #endif WORKID widMax = indSnap.MaxWorkId(); ciDebugOut (( DEB_ITRACE, "Max work id %ld\n", widMax )); SetMaxWorkId ( widMax ); CFreshTest* pFreshTest = indSnap.GetFresh(); CKeyBuf keyLast; CMergeSourceCursor pCurSrc( indSnap ); if ( !pCurSrc.IsEmpty() ) { // // Read-ahead on the source indexes results in better merge // performance, but slower queries. Temporarily switch modes. // CSetReadAhead readAhead( _storage ); CPersComp compr( _xPhysIndex.GetReference(), _widMax ); ciDebugOut (( DEB_ITRACE, "widMax passed to compressor: %ld\n", _widMax )); const CKeyBuf * pKey; ULONG page = ULONG(-1); BitOffset bitOff; #if CIDBG == 1 keyLast.SetPid(pidContents); // arbitrary but not pidAll #endif #if CIDBG == 1 WCHAR FirstLetter = '@'; #endif mergeProgress.Update( 0 ); ciDebugOut (( DEB_ITRACE,"Merging. Merge on letter: ")); for (pKey = pCurSrc->GetKey(); pKey != NULL; pKey = pCurSrc->GetNextKey()) { #if CIDBG == 1 cKey++; if ( *(pKey->GetStr()) != FirstLetter ) { FirstLetter = *(pKey->GetStr()); if ( FirstLetter < L'~' ) ciDebugOut(( DEB_NOCOMPNAME | DEB_ITRACE | DEB_PENDING, "%c", FirstLetter )); else ciDebugOut(( DEB_NOCOMPNAME | DEB_ITRACE | DEB_PENDING, "<%04x>", FirstLetter )); } #endif // // Don't store empty keys // WORKID wid = pCurSrc->WorkId(); if ( wid == widInvalid ) continue; // // Add the key to the new index. // // This would later lead to a divide by 0 Win4Assert( 0 != pCurSrc->WorkIdCount() ); compr.PutKey(pKey, pCurSrc->WorkIdCount(), bitOff); for ( ; wid != widInvalid; wid = pCurSrc->NextWorkId()) { // fresh test CFreshTest::IndexSource indexSrc = pFreshTest->IsCorrectIndex (pCurSrc->IndexId(), wid); // // There should always be an entry for a workid in the fresh // test whose data is contained in a wordlist/shadow-index. // Win4Assert( CFreshTest::Master != indexSrc ); if ( CFreshTest::Invalid != indexSrc ) { compr.PutWorkId(wid, pCurSrc->MaxOccurrence(), pCurSrc->OccurrenceCount()); for (OCCURRENCE occ = pCurSrc->Occurrence(); occ != OCC_INVALID; occ = pCurSrc->NextOccurrence()) { compr.PutOccurrence(occ); } } } // // If this key didn't have any wids then we can delete it from // the new index. Otherwise, track it as a possible splitkey // and update the index as necessary. // if ( bitOff.Page() != page ) { page = bitOff.Page(); _xDir->Add ( bitOff, *pKey ); } else { Win4Assert( page == _xDir->GetBitOffsetLastAdded().Page() ); } keyLast = *pKey; // // There's no point in special abort code. We have to handle // exceptions anyway. // if ( _fAbortMerge || partn.IsCleaningUp() ) { ciDebugOut(( DEB_ITRACE, "Aborting Merge\n" )); THROW( CException( STATUS_UNSUCCESSFUL ) ); } mergeProgress.Update( _xPhysIndex->PagesInUse() * 100 / _xPhysIndex->PageSize() ); } ciDebugOut(( DEB_ITRACE | DEB_PENDING, "%d keys in index\n", cKey )); // add sentinel key keyLast.FillMax(); keyLast.SetPid( pidContents ); compr.PutKey( &keyLast, 1, bitOff ); // // If the MaxKey is the first key on a page, it must be added // to the directory. // if ( bitOff.Page() != page ) { page = bitOff.Page(); _xDir->Add ( bitOff, keyLast ); } else { Win4Assert( page == _xDir->GetBitOffsetLastAdded().Page() ); } mergeProgress.Update( 100 ); compr.FreeStream(); } // compr goes out of scope else { ciDebugOut (( DEB_ITRACE, "No merge cursor created\n" )); CPersComp compr( _xPhysIndex.GetReference(), _widMax ); keyLast.FillMax(); keyLast.SetPid(pidContents); BitOffset bitOff; compr.PutKey( &keyLast, 1, bitOff ); compr.FreeStream(); } // // Compressor MUST NOT be in scope here. // keyLast.FillMax(); _xDir->LokFlushDir( keyLast ); _xDir->LokBuildDir( keyLast ); // after compr is dead _xPhysIndex->Flush(); _xPhysIndex->Reopen( FALSE ); } //Merge //+--------------------------------------------------------------------------- // // Member: CPersIndex::QueryCursor, public // // Synopsis: Return a cursor for the whole persistent index. // // Returns: A new cursor. // // History: 24-Apr-91 KyleP Created. // //---------------------------------------------------------------------------- CKeyCursor * CPersIndex::QueryCursor() { CKey key; key.FillMin(); return QueryKeyCursor( &key ); } //+--------------------------------------------------------------------------- // // Member: CPersIndex::QueryKeyCursor, public // // Synopsis: Return a key cursor for the shadow index when restarting a // master merge // // Returns: A new cursor. // // History: 12-Apr-94 DwightKr Created. // //---------------------------------------------------------------------------- CKeyCursor * CPersIndex::QueryKeyCursor(const CKey * pKey) { BitOffset posKey; CKeyBuf keyInit; _xDir->Seek( *pKey, &keyInit, posKey ); ciDebugOut(( DEB_ITRACE, "found key %.*ws at %lx:%lx\n", keyInit.StrLen(), keyInit.GetStr(), posKey.Page(), posKey.Offset() )); XPtr xCursor( new CPersDeComp( _xDir.GetReference(), GetId(), _xPhysIndex.GetReference(), posKey, keyInit, pKey, _widMax ) ); if ( 0 == xCursor->GetKey() ) xCursor.Free(); return xCursor.Acquire(); } //QueryKeyCursor //+--------------------------------------------------------------------------- // // Member: CPersIndex::QueryCursor, public // // Synopsis: Return a cursor for the persistent index. // // Arguments: [pKey] -- Key to initially seek for. // [isRange] -- TRUE for range query // [cMaxNodes] -- Max number of nodes to create. Decremented // on return. // // Returns: A new cursor. // // History: 24-Apr-91 KyleP Created. // //---------------------------------------------------------------------------- COccCursor * CPersIndex::QueryCursor( const CKey * pKey, BOOL isRange, ULONG & cMaxNodes ) { Win4Assert( cMaxNodes > 0 ); if (isRange) { CKey keyEnd; keyEnd.FillMax (*pKey); return QueryRangeCursor (pKey, &keyEnd, cMaxNodes); } if (pKey->Pid() == pidAll) return QueryRangeCursor ( pKey, pKey, cMaxNodes ); cMaxNodes--; if ( 0 == cMaxNodes ) { ciDebugOut(( DEB_WARN, "Node limit reached in CPersIndex::QueryCursor.\n" )); THROW( CException( STATUS_TOO_MANY_NODES ) ); } BitOffset posKey; CKeyBuf keyInit; _xDir->Seek( *pKey, &keyInit, posKey ); ciDebugOut(( DEB_ITRACE, "found key %.*ws at %lx:%lx\n", keyInit.StrLen(), keyInit.GetStr(), posKey.Page(), posKey.Offset() )); XPtr xCursor( new CPersDeComp( _xDir.GetReference(), GetId(), _xPhysIndex.GetReference(), posKey, keyInit, pKey, _widMax ) ); if ( xCursor->GetKey() == 0 || !pKey->MatchPid( *xCursor->GetKey()) || pKey->CompareStr(*xCursor->GetKey()) != 0 ) { xCursor.Free(); } return xCursor.Acquire(); } //QueryCursor //+--------------------------------------------------------------------------- // // Member: CPersIndex::QueryRangeCursor, public // // Synopsis: Return a range cursor for the persistent index. // // Arguments: [pKey] -- Key at beginning of the range. // [pKeyEnd] -- Key at the end of the range. // [cMaxNodes] -- Max number of nodes to create. Decremented // on return. // // Returns: A new cursor. // // History: 11-Dec-91 AmyA Created. // 31-Jan-92 AmyA Moved code to CreateRange(). // //---------------------------------------------------------------------------- COccCursor * CPersIndex::QueryRangeCursor( const CKey * pKey, const CKey * pKeyEnd, ULONG & cMaxNodes ) { Win4Assert( cMaxNodes > 0 ); COccCurStack curStk; CreateRange(curStk, pKey, pKeyEnd, cMaxNodes ); return curStk.QuerySynCursor( MaxWorkId() ); } //+--------------------------------------------------------------------------- // // Member: CPersIndex::QuerySynCursor, public // // Synopsis: Return a synonym cursor for the persistent index. // // Arguments: [keyStk] -- Stack of keys to be searched for. // [isRange] -- Whether or not this is a range search. // [cMaxNodes] -- Max number of nodes to create. Decremented // on return. // // Returns: A new cursor. // // History: 31-Jan-92 AmyA Created. // //---------------------------------------------------------------------------- COccCursor * CPersIndex::QuerySynCursor( CKeyArray & keyArr, BOOL isRange, ULONG & cMaxNodes ) { COccCurStack curStk; int keyCount = keyArr.Count(); for (int i = 0; i < keyCount; i++) { Win4Assert( cMaxNodes > 0 ); CKey& key = keyArr.Get(i); if (isRange) { CKey keyEnd; keyEnd.FillMax(key); CreateRange(curStk, &key, &keyEnd, cMaxNodes ); } else if ( key.Pid() == pidAll ) { CreateRange ( curStk, &key, &key, cMaxNodes ); } else { XPtr xCursor( QueryCursor( &key, FALSE, cMaxNodes ) ); if ( !xCursor.IsNull() ) { curStk.Push( xCursor.GetPointer() ); xCursor.Acquire(); } } } return(curStk.QuerySynCursor( MaxWorkId())); } //+--------------------------------------------------------------------------- // // Member: CPersIndex::CreateRange, private // // Synopsis: Adds all cursors with keys between pKey and pKeyEnd to curStk. // // Arguments: [curStk] -- CKeyCurStack to add cursors to. // [pKey] -- Key at beginning of range. // [pKeyEnd] -- End of key range. // [cMaxNodes] -- Max number of nodes to create. Decremented // on return. // // History: 31-Jan-92 AmyA Created. // //---------------------------------------------------------------------------- void CPersIndex::CreateRange( COccCurStack & curStk, const CKey * pKeyStart, const CKey * pKeyEnd, ULONG & cMaxNodes ) { Win4Assert( cMaxNodes > 0 ); cMaxNodes--; if ( 0 == cMaxNodes ) { ciDebugOut(( DEB_WARN, "Node limit reached in CPersIndex::CreateRange.\n" )); THROW( CException( STATUS_TOO_MANY_NODES ) ); } BitOffset posKey; CKeyBuf keyInit; _xDir->Seek( *pKeyStart, &keyInit, posKey ); ciDebugOut (( DEB_ITRACE, "CreateRange %.*ws-%.*ws. Dir seek %.*ws, pid %d\n", pKeyStart->StrLen(), pKeyStart->GetStr(), pKeyEnd->StrLen(), pKeyEnd->GetStr(), keyInit.StrLen(), keyInit.GetStr(), keyInit.Pid() )); CPersDeComp* pCursor = new CPersDeComp( _xDir.GetReference(), GetId(), _xPhysIndex.GetReference(), posKey, keyInit, pKeyStart, _widMax); XPtr xCursor( pCursor ); const CKeyBuf * pKeyCurrent = pCursor->GetKey(); if ( 0 == pKeyCurrent ) return; PROPID pid = pKeyStart->Pid(); curStk.Push(pCursor); xCursor.Acquire(); ciDebugOut(( DEB_ITRACE, "First key %.*ws, pid %d\n", pKeyCurrent->StrLen(), pKeyCurrent->GetStr(), pKeyCurrent->Pid() )); do { if (pid != pidAll) // exact pid match { // skip wrong pids while (pid != pKeyCurrent->Pid()) { #if CIDBG == 1 //------------------------------------------ if (pKeyCurrent) { ciDebugOut(( DEB_ITRACE, " skip: %.*ws, pid %d, wid %d\n", pKeyCurrent->StrLen(), pKeyCurrent->GetStr(), pKeyCurrent->Pid(), pCursor->WorkId() )); } else ciDebugOut(( DEB_ITRACE, " key\n" )); #endif //-------------------------------------------------- pKeyCurrent = pCursor->GetNextKey(); if (pKeyCurrent == 0 || pKeyEnd->CompareStr(*pKeyCurrent) < 0 ) break; } // either pid matches or we have overshot // i.e. different pids and current string > end } if (pKeyCurrent == 0 || !pKeyEnd->MatchPid (*pKeyCurrent) || pKeyEnd->CompareStr (*pKeyCurrent) < 0 ) { break; // <--- LOOP EXIT } cMaxNodes--; if ( 0 == cMaxNodes ) { ciDebugOut(( DEB_WARN, "Node limit reached in CPersIndex::CreateRange.\n" )); THROW( CException( STATUS_TOO_MANY_NODES ) ); } // Clone the previous cursor... pCursor = new CPersDeComp(*pCursor); xCursor.Set( pCursor ); // Add it to avoid memory leaks if GetNextKey fails curStk.Push(pCursor); // may be wrong pid xCursor.Acquire(); // increment the added cursor pKeyCurrent = pCursor->GetNextKey(); #if CIDBG == 1 if (pKeyCurrent) { ciDebugOut(( DEB_ITRACE, " %.*ws, wid %d\n", pKeyCurrent->StrLen(), pKeyCurrent->GetStr(), pCursor->WorkId() )); } else ciDebugOut(( DEB_ITRACE, " key\n" )); #endif } while ( pKeyCurrent ); // Since we have one more cursor in curStk than we wanted... curStk.DeleteTop(); } //+--------------------------------------------------------------------------- // // Member: CPersIndex::Remove, public // // Synopsis: Remove index from storage // // History: 02-May-91 BartoszM Created. // //---------------------------------------------------------------------------- void CPersIndex::Remove() { _xPhysIndex->Close(); _xDir->Close(); _obj->Close(); if ( !_storage.RemoveObject( ObjectId() ) ) { DWORD dwError = GetLastError(); ciDebugOut(( DEB_ERROR, "Delete of index %08x failed: %d\n", ObjectId(), dwError )); } } #ifdef KEYLIST_ENABLED //+--------------------------------------------------------------------------- // // Member: CPersIndex::AcquireRelevantWords, public // // Synopsis: Return relevant word key ids computed at the most recent // master merge. The caller must delete the object returned. // // Returns: CRWStore * // // History: 25-Apr-94 v-dlee Created // //---------------------------------------------------------------------------- CRWStore * CPersIndex::AcquireRelevantWords() { CRWStore *p = _pRWStore; ciDebugOut (( DEB_ITRACE,"CPersIndex::acquire _pRWStore: %lx\n",_pRWStore)); _pRWStore = 0; return p; } //AcquireRelevantWords //+--------------------------------------------------------------------------- // // Member: CPersIndex::ComputeRelevantWords, public // // Synopsis: Compute and return relevant word key ids // // Arguments: [cRows] -- # of wids in pwid array // [cRW] -- max # of rw keys per wid // [pwid] -- an array of wids in increasing order whose // rw key ids are to be returned // [pKeyList] -- keylist to use in translation of keys to ids // // Returns: CRWStore * // // History: 25-Apr-94 v-dlee Created // //---------------------------------------------------------------------------- CRWStore * CPersIndex::ComputeRelevantWords(ULONG cRows,ULONG cRW, WORKID *pwid,CKeyList *pKeyList) { ciDebugOut((DEB_ITRACE,"ComputeRelevantWords top\n")); // // Get the resources needed to do the computation // CRelevantWord RelWord(pwid,cRows,cRW); CPersIndexCursor indCur(this); CKeyListCursor keylCur(pKeyList); // // Walk through the index and find occurances of keys in the wids // const CKeyBuf * pKey, * pklKey; for (pKey = indCur->GetKey(), pklKey = keylCur->GetKey(); pKey != 0; pKey = indCur->GetNextKey()) { if (pKey->Pid() == pidContents && ((CKeyBuf * const) pKey)->IsPossibleRW()) { ULONG cWids = 0; for (WORKID wid = indCur->WorkId(); wid != widInvalid; wid = indCur->NextWorkId()) { cWids++; if (RelWord.isTrackedWid(wid)) RelWord.Add(wid,indCur->OccurrenceCount()); } // // Walk the keylist until we match it up with where the // index cursor is. // while (pklKey->CompareStr(*pKey) != 0) pklKey = keylCur->GetNextKey(); RelWord.DoneWithKey(pklKey->Pid(),MaxWorkId(),cWids); } } return RelWord.AcquireStore(); } //ComputeRelevantWords #endif // KEYLIST_ENABLED //+--------------------------------------------------------------------------- // // Member: CPersIndex::MakeBackupCopy // // Synopsis: Makes a copy of the index and directory using the storage // provided. // // Arguments: [storage] - Storage // // History: 3-17-97 srikants Created // //---------------------------------------------------------------------------- void CPersIndex::MakeBackupCopy( PStorage & storage, WORKID wid, PSaveProgressTracker & tracker ) { // // Create an index in the destination storage. // CPersIndex * pDstIndex = new CPersIndex( storage, wid, GetId(), _xPhysIndex->PageSize(), IsMaster() ? eMaster : eShadow ); XPtr xDstIndex( pDstIndex ); // // Make a backup copy of the stream. // _xPhysIndex->MakeBackupCopy( pDstIndex->_xPhysIndex.GetReference(), tracker ); // Make a backup copy of the directory. // _xDir->MakeBackupCopy( storage, tracker ); } #if CIDBG == 1 //+--------------------------------------------------------------------------- // // Member: CDiskIndex::VerifyContents, public // // Synopsis: Walks through an index and thus verifies each key // // History: 28-Oct-94 DwightKr Created // //---------------------------------------------------------------------------- void CDiskIndex::VerifyContents() { // // Turn this on when we think we are missing keys. // #if 0 CKeyCursor *pCursor = QueryCursor(); if ( pCursor ) { TRY { ciDebugOut((DEB_ITRACE, "Verifying contents of new index\n")); WCHAR FirstLetter = '@'; for ( const CKeyBuf * pKey = pCursor->GetKey(); pKey != NULL; pKey = pCursor->GetNextKey()) { if ( *(pKey->GetStr()) != FirstLetter ) { FirstLetter = *(pKey->GetStr()); if ( FirstLetter < L'~' ) ciDebugOut(( DEB_NOCOMPNAME | DEB_ITRACE | DEB_PENDING, "%c", FirstLetter )); else ciDebugOut(( DEB_NOCOMPNAME | DEB_ITRACE | DEB_PENDING, "<%04x>", FirstLetter )); } } ciDebugOut(( DEB_NOCOMPNAME | DEB_ITRACE | DEB_PENDING, "\n" )); } CATCH (CException, e) { ciDebugOut(( DEB_ERROR, "Error 0x%x while verifying contents of new index\n", e.GetErrorCode() )); } END_CATCH delete pCursor; } #endif // 0 } #endif