windows-nt/Source/XPSP1/NT/inetsrv/query/bigtable/bigtable.cxx
2020-09-26 16:20:57 +08:00

4028 lines
120 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1994 - 2000.
//
// File: bigtable.cxx
//
// Contents:
//
// Classes: CLargeTable - top-level class for large tables
// CTableSegIter - iterator of table segments
//
// Functions:
//
// History: 01 Feb 1994 AlanW Created
//
//--------------------------------------------------------------------------
#include "pch.cxx"
#pragma hdrstop
#include <query.hxx>
#include <srequest.hxx>
#include <cifailte.hxx>
#include <tbrowkey.hxx>
#include "tabledbg.hxx"
#include "tblwindo.hxx"
#include "winsplit.hxx"
#include "buketize.hxx"
#include "tputget.hxx"
#include "regtrans.hxx"
static inline ULONG AbsDiff( ULONG num1, ULONG num2 )
{
return num1 >= num2 ? num1-num2 : num2-num1;
}
unsigned CTableSink::LokCategorize(
CCategParams & params )
{
return _pCategorizer->LokAssignCategory( params );
} //LokCategorize
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::CLargeTable, public
//
// Synopsis: Constructor for a large table. Allocates and fills
// in the master column description.
// Allocates initial window to collect data.
//
// Arguments: [col] - A description of initial output column set
// [sort] - A description of the initial sort order
// [cCategorizers] - Total count of categorizers over table
// [mutex] - CAsyncQuery's mutex for serialization
// [fUniqueWorkid] - TRUE if workid (from iterator) is unique
//
// Notes:
//
// History: 01-Jan-96 KyleP Optional unique wid in user-mode.
//
//--------------------------------------------------------------------------
CLargeTable::CLargeTable( XColumnSet & col,
XSortSet & sort,
unsigned cCategorizers,
CMutexSem & mutex,
BOOL fUniqueWorkid,
CRequestServer * pQuiesce )
: CTableSink(),
_sigLargeTable(eSigLargeTable),
_cbMemoryUsage( 0 ),
_cbMemoryTarget( DEFAULT_MEM_TARGET ),
_MasterColumnSet( col.GetPointer() ),
_fUniqueWorkid( fUniqueWorkid ),
_segListMgr(cMaxClientEntriesToPin),
_segList(_segListMgr.GetList()),
_watchList(_segList),
_nextSegId(1),
_pCategorization(0),
_fAbort(FALSE),
_pSortSet( 0 ),
_fProgressNeeded (FALSE),
_bitNotifyEnabled(0),
_bitClientNotified(0),
_bitChangeQuiesced(0),
_bitRefresh(0),
_bitIsWatched(0),
_bitQuiesced(0),
_pDeferredRows(0),
_fQuiescent (FALSE),
_ulProgressNum(0),
_ulProgressDenom(1),
_cCategorizersTotal( cCategorizers ),
_fRankVectorBound( FALSE ),
_mutex( mutex ),
_widCurrent( WORKID_TBLBEFOREFIRST ),
_hNotifyEvent( 0 ),
_pRequestServer( 0 ),
_pQExecute(0),
_pQuiesce( pQuiesce ),
_fSortDefined( FALSE )
{
tbDebugOut (( DEB_NOTIFY, "lt: CLargeTable\n" ));
TRY // use exception generating new
{
// Don't bucketize when rank vector is bound
_fRankVectorBound = ( 0 != _MasterColumnSet.Find( pidRankVector ) );
//
// Be sure the workid column is in the master column set. The
// output column set was added in the constructor above.
//
_MasterColumnSet.Add( CColumnMasterDesc(pidWorkId, TYPE_WORKID) );
//
// Add the status column, which is used internally and may
// be bound to at some point. Status is stored as a byte to save
// space, and is translated to an HRESULT when passed out to a
// client.
//
CColumnMasterDesc *pRowStatus =
_MasterColumnSet.Add( CColumnMasterDesc(pidRowStatus, VT_UI1) );
pRowStatus->SetComputed(TRUE);
pRowStatus->SetUniform(TRUE);
//
// If categorization is turned on, create an I4 category column
//
if ( 0 != cCategorizers )
_MasterColumnSet.Add( CColumnMasterDesc(pidChapter, VT_I4) );
//
// Set up file path and name as global, shared compressions with
// the WorkId as key. Only needed if it's inconvenient to fetch
// name and path from workid (e.g. workid isn't unique).
//
if ( !_fUniqueWorkid )
{
_MasterColumnSet.Add( CColumnMasterDesc(pidPath, TYPE_PATH) );
_MasterColumnSet.Add( CColumnMasterDesc(pidName, TYPE_NAME) );
CCompressedCol * pPathCompression = new CPathStore();
CColumnMasterDesc* pMastCol;
pMastCol = _MasterColumnSet.Find(pidWorkId);
Win4Assert(pMastCol != 0);
pMastCol->SetCompression(pPathCompression);
pMastCol = _MasterColumnSet.Find(pidPath);
Win4Assert(pMastCol != 0);
pMastCol->SetCompression(pPathCompression, pidWorkId);
pMastCol = _MasterColumnSet.Find(pidName);
Win4Assert(pMastCol != 0);
pMastCol->SetCompression(pPathCompression, pidWorkId);
}
//
// Add the sort keys to the master column set.
//
if ( ! sort.IsNull() )
{
for ( unsigned iCol = 0; iCol < sort->Count(); iCol++ )
_MasterColumnSet.Add( CColumnMasterDesc(sort->Get(iCol)) );
_fSortDefined = TRUE; // set it to true since sorting is defined
}
//
// Master column set is constructed. Acquire the sortset, but first
// make sure workid is in the sort set.
//
_pSortSet = _CheckAndAddWidToSortSet( sort );
Win4Assert ( 0 != _pSortSet );
tbDebugOut(( DEB_ITRACE, "New Big Table with %d columns\n",
_MasterColumnSet.Size() ));
}
CATCH(CException, e)
{
delete _pSortSet;
RETHROW();
}
END_CATCH;
}
//+---------------------------------------------------------------------------
//
// Function: _CheckAndAddWidToSortSet
//
// Synopsis: Tests if the sort specification(if any) already had the
// "pidWorkId" as part of the sort set. If not, it adds one to
// the end of sort set.
//
// Arguments: [sort] - Input sort set.
//
// Returns: The sort set to be used.
//
// History: 3-22-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
CSortSet * CLargeTable::_CheckAndAddWidToSortSet( XSortSet & sort )
{
//
// Check if the pidWorkId is already a field in the sortset.
//
BOOL fPresent = FALSE;
if ( sort.IsNull() )
{
sort.Set( new CSortSet(1) );
}
else
{
for ( unsigned i = 0; i < sort->Count(); i++ )
{
SSortKey & key = sort->Get(i);
if ( pidWorkId == key.pidColumn )
{
fPresent = TRUE;
break;
}
}
}
if ( !fPresent )
{
SSortKey keyWid( pidWorkId, QUERY_SORTASCEND, 0 );
sort->Add( keyWid, sort->Count() );
}
//
// Initialize the variant types array for the sort columns
//
_vtInfoSortKey.Init( sort->Count() );
for ( unsigned i = 0; i < sort->Count(); i++ )
{
SSortKey & key = sort->Get(i);
PROPID pid = key.pidColumn;
CColumnMasterDesc *pMasterCol = _MasterColumnSet.Find(pid);
Win4Assert( 0 != pMasterCol );
_vtInfoSortKey[i] = pMasterCol->DataType;
}
_keyCompare.Set( new CTableKeyCompare( sort.GetReference() ) );
_currRow.Set( new CTableRowKey( sort.GetReference() ) );
_segListMgr.GetSegmentArray().SetComparator( _keyCompare.GetPointer() );
return sort.Acquire();
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::~CLargeTable, public
//
// Synopsis: Destructor for a large table.
//
// Notes: The query execution object along with
// the worker threads has been already
// destroyed (see CAsyncQuery) so there
// is no race condition here.
//
//--------------------------------------------------------------------------
CLargeTable::~CLargeTable( )
{
//
// Cancel the notification. Insure that no notifications will be
// picked up as the thread leaves. In almost all cases, this will
// never be called, because the notification thread will already
// be killed when the last connection point goes away.
//
Win4Assert( 0 == _pQExecute );
CancelAsyncNotification();
delete _pSortSet;
// make sure the waiter wakes up
if ( 0 != _pQuiesce )
{
// only called on failure cases -- success cases quiesce ok
_pQuiesce->QueryQuiesced( _fQuiescent, _scStatus );
_pQuiesce = 0;
}
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::_LokFindTableSegment, private
//
// Synopsis: Find the appropriate table segment for a request which
// operates on only a single table segment.
//
// Arguments: [wid] - WORKID which identifies the table segment of interest
// [fMustExist] - TRUE if wid must exist in some segment
// [pSegHint] - optional, possible segment in which wid will be
// found.
//
// Returns: CTableSegment* - the selected table segment, 0 if not found.
//
// Notes: The method cannot be used for any of the special workIDs used
// as sentinels (WORKID_TBLBEFOREFIRST, etc).
// Use _LokLocateTableSegment instead.
//
//--------------------------------------------------------------------------
CTableSegment *
CLargeTable::_LokFindTableSegment(
WORKID wid
)
{
//
// Iterate over all the segments and locate the segment in
// which the given workid is present.
//
for ( CFwdTableSegIter iter(_segList); !_segList.AtEnd(iter) ;
_segList.Advance(iter) )
{
CTableSegment & segment = *iter.GetSegment();
if ( segment.IsRowInSegment( wid ) )
break;
}
CTableSegment * pSeg = 0;
if ( !_segList.AtEnd(iter) )
{
pSeg = iter.GetSegment();
}
return pSeg;
}
//+---------------------------------------------------------------------------
//
// Function: _LokSplitWindow
//
// Synopsis: Splits the given window into two and replaces the source
// window with two windows.
//
// Arguments: [ppWindow] - Source window that needs to be split.
// If successful, will be set to NULL on
// return.
// [iSplitQuery] - Offset in the rowIndex that is used by
// query as the split point.
//
// Returns: Pointer to the "left" window after split.
//
// History: 2-06-95 srikants Created
//
// Notes: Destroys the source window after split.
//
//----------------------------------------------------------------------------
CTableSegment * CLargeTable::_LokSplitWindow( CTableWindow ** ppWindow,
ULONG iSplitQuery )
{
Win4Assert( 0 != ppWindow );
CTableWindow * pWindow = *ppWindow;
Win4Assert( 0 != pWindow );
CTableWindow * pLeft = 0;
CTableWindow * pRight = 0;
{
CTableWindowSplit split( *pWindow,
iSplitQuery,
pWindow->GetSegId(), _AllocSegId(),
_segList.IsLast( *pWindow ) );
//
// Create empty target windows.
//
split.CreateTargetWindows();
tbDebugOut(( DEB_WINSPLIT, "CLargeTable::Splitting Window\n" ));
//
// Do the actual split.
//
split.DoSplit();
//
// Take ownership of the newly created windows.
//
split.TransferTargetWindows( &pLeft, &pRight );
Win4Assert( 0 != pLeft && 0 != pRight );
}
//
// Replace the pWindow in the list with the two new ones.
//
CTableSegList windowList;
windowList.Push( pRight );
windowList.Push( pLeft );
_segListMgr.Replace( pWindow, windowList );
Win4Assert( windowList.IsEmpty() );
//
// Update the watch regions from the source window to destination
// window.
//
for ( CWatchIter iter(_watchList) ;
!_watchList.AtEnd(iter);
_watchList.Advance(iter) )
{
HWATCHREGION hWatch = iter->Handle();
CWatchRegion * pRegion = iter.Get();
if ( pRegion->Segment() == pWindow )
{
CTableWindow * pNewStartWindow;
if ( pLeft->HasWatch(hWatch) )
{
pNewStartWindow = pLeft;
}
else
{
Win4Assert( pRight->HasWatch(hWatch) );
pNewStartWindow = pRight;
}
ULONG iWatch = (ULONG) pNewStartWindow->GetWatchStart(hWatch);
CI_TBL_BMK bmkNew = pNewStartWindow->GetBookMarkAt( iWatch );
iter->UpdateSegment( pWindow, pNewStartWindow, bmkNew );
}
#if CIDBG==1
_watchList.CheckRegionConsistency( pRegion );
#endif // CIDBG==1
}
//
// Destroy the source window.
//
delete pWindow;
*ppWindow = 0;
//
// Update the mru cache to reflect the split.
//
_segListMgr.UpdateSegsInUse( pLeft );
_segListMgr.UpdateSegsInUse( pRight );
return pRight;
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::PathToWorkID, public
//
// Synopsis: Convert a file path name to a work ID. For down-level
// stores, the file systems do not return a value which
// can be reliably used as a WorkID, so we use the file
// path name as the unique identifier of a file. This
// method will use the path column compressor to provide
// a unique ID over the table given the input path name.
//
// Arguments: [obj] -- a reference to an object retriever which can
// return object data
// [eRowType] -- the type of row being added.
//
// Returns: WORKID - a unique value over the table for this row.
//
// Notes:
//
//--------------------------------------------------------------------------
WORKID CLargeTable::PathToWorkID( CRetriever& obj,
CTableSink::ERowType eRowType )
{
Win4Assert( !_fUniqueWorkid );
if ( _fUniqueWorkid )
{
PROPVARIANT var;
ULONG cb = sizeof(var);
if ( obj.GetPropertyValue( pidWorkId, &var, &cb ) != GVRSuccess )
return widInvalid;
else
{
Win4Assert( var.vt == VT_I4 );
return var.lVal;
}
}
else
{
WORKID ulRet = 0;
GetValueResult eGvr;
CColumnMasterDesc* pMastCol;
CLock lock(_mutex);
pMastCol = _MasterColumnSet.Find( pidPath );
Win4Assert(pMastCol != NULL && pMastCol->IsCompressedCol());
struct
{
PROPVARIANT v;
WCHAR awch[512]; // don't force new every time
} varnt;
PROPVARIANT* pVarnt = &(varnt.v);
ULONG cbBuf = sizeof varnt;
XArray<BYTE> xByte;
eGvr = obj.GetPropertyValue(pMastCol->PropId, pVarnt, &cbBuf);
if (eGvr == GVRNotEnoughSpace)
{
Win4Assert(cbBuf <= TBL_MAX_DATA + sizeof (PROPVARIANT));
pVarnt = (CTableVariant *) new BYTE[cbBuf];
xByte.Set( cbBuf, (BYTE *)pVarnt );
Win4Assert (pVarnt != NULL);
eGvr = obj.GetPropertyValue(pMastCol->PropId,
pVarnt, &cbBuf);
}
if ( GVRSuccess != eGvr )
{
THROW( CException(CRetriever::NtStatusFromGVR(eGvr)) );
}
BOOL fFound = FALSE;
if ( CTableSink::eNewRow != eRowType )
{
// try to find an existing path before adding it
fFound = pMastCol->GetCompressor()->FindData( pVarnt, ulRet );
}
if ( ! fFound )
pMastCol->GetCompressor()->AddData(pVarnt, &ulRet, eGvr);
Win4Assert(eGvr == GVRSuccess && ulRet != 0);
return ulRet;
}
} //PathToWorkID
//+---------------------------------------------------------------------------
//
// Function: WorkIdToPath
//
// Synopsis: Converts a workid to a path.
//
// Arguments: [wid] - WID whose path is needed
// [outVarnt] - on output will have the path as a variant
// [cbVarnt] - in/out max len on input; actual len on
// output. If the return value is FALSE, this will
// indicate the lenght of variant needed. If on
// output this is 0 and the return value is FALSE,
// wid->path operation failed.
//
// Returns: TRUE if we succeeded in getting the path.
// FALSE if we failed.
//
// History: 3-24-95 srikants Created
//
//----------------------------------------------------------------------------
BOOL CLargeTable::WorkIdToPath( WORKID wid, CInlineVariant & outVarnt,
ULONG & cbVarnt )
{
Win4Assert( !_fUniqueWorkid );
if ( _fUniqueWorkid )
return FALSE;
CLock lock( _mutex );
CColumnMasterDesc* pMastCol = _MasterColumnSet.Find( pidPath );
Win4Assert( pMastCol );
CCompressedCol & pathCompressor = *(pMastCol->GetCompressor());
CTableVariant pathVarnt;
XCompressFreeVariant xpvarnt;
BOOL fStatus = FALSE;
if ( GVRSuccess ==
pathCompressor.GetData( &pathVarnt, VT_LPWSTR, wid, pidPath ) )
{
xpvarnt.Set( &pathCompressor, &pathVarnt );
//
// Copy the data from the variant to the buffer.
//
const ULONG cbHeader = sizeof(CInlineVariant);
ULONG cbVarData = pathVarnt.VarDataSize();
ULONG cbTotal = cbVarData + cbHeader;
if ( cbVarnt >= cbTotal )
{
CVarBufferAllocator bufAlloc( outVarnt.GetVarBuffer(), cbVarData );
bufAlloc.SetBase(0);
pathVarnt.Copy( &outVarnt, bufAlloc, (USHORT) cbVarData, 0 );
fStatus = TRUE;
}
cbVarnt = cbTotal;
}
else
{
cbVarnt = 0;
}
return fStatus;
} //WorkIdToPath
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::_LokRemoveCategorizedRow, private
//
// Synopsis: Removes a row from the categorization.
//
// Arguments: [chapt] -- chapter from which removal is done
// [wid] -- wid to remove
// [widNext] -- the next wid in the table, can be widInvalid
// [pSegment] -- segment from which widNext can be computed if
// not specified.
//
// History: ? dlee Created
//
//--------------------------------------------------------------------------
void CLargeTable::_LokRemoveCategorizedRow(
CI_TBL_CHAPT chapt,
WORKID wid,
WORKID widNext,
CTableSegment * pSegment )
{
if ( IsCategorized() )
{
if ( widInvalid == widNext )
{
// sigh. We need to find the workid of the row after the
// row just deleted in the case that the deleted row was the
// first row in a category and not the only row in the category,
// since the categorizers need to keep track of the first wid
// in a category. widNext is widInvalid if the deleted row
// was the last row in the window.
for ( CFwdTableSegIter iter( _segList );
!_segList.AtEnd( iter );
_segList.Advance( iter ) )
{
CTableSegment * pNextSeg = iter.GetSegment();
if ( pNextSeg == pSegment )
{
_segList.Advance( iter );
if ( !_segList.AtEnd( iter ) )
{
CTableWindow * pWindow = iter.GetWindow();
widNext = pWindow->GetFirstBookMark();
}
break;
}
_segList.Advance(iter);
}
}
pSegment->GetCategorizer()->RemoveRow( chapt, wid, widNext );
}
} //_LokRemoveCategorizedRow
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::PutRow, public
//
// Synopsis: Add a row to a large table
//
// Arguments: [obj] -- a reference to an object retriever which can
// return object data
// [eRowType] -- the type of row being added.
//
// Returns: TRUE if progress report needed
//
//--------------------------------------------------------------------------
BOOL CLargeTable::PutRow( CRetriever& obj, CTableSink::ERowType eRowType )
{
CReleasableLock relLock( _mutex, FALSE );
if ( FALSE == relLock.Try() )
{
// Unable to get bigtable lock. Maybe GetRows is holding bigtable
// lock and is waiting on propstore lock which this thread might
// be holding. So rlease that and try again.
obj.Quiesce();
relLock.Request(); // If we deadlock now, then we need to fix that !
}
//
// The query may be in the process of being deleted if it has been
// cancelled during query execution. In this case, the _pQExecute will
// have been set to 0 by the call to ReleaseQueryExecute() in
// ~CAsyncQuery.
//
if ( 0 == _pQExecute )
return FALSE;
TRY
{
WORKID widRow = obj.WorkId();
//
// Check if it already exists in one of the segments based
// on its workid.
//
CTableSegment *pSegment = 0;
if ( CTableSink::eNotificationRow == eRowType )
{
//
// If the table is not watched, do not process notifications.
//
if ( !_LokIsWatched() )
{
return _fProgressNeeded;
}
else if ( _LokIsPutRowDeferred( widRow, obj ) )
{
_bitRefresh = 1;
LokCompleteAsyncNotification();
return _fProgressNeeded;
}
}
else if ( CTableSink::eNewRow != eRowType )
{
//
// NOSUPPORT: This will not work with LINKS. We have to look at
// table irrespective of whether this is a notification or not.
// Of course, we don't support links.
//
//
pSegment = _LokFindTableSegment( obj.WorkId() );
}
if ( 0 != pSegment )
{
//
// Modifications are to be treated as deletions followed by
// additions.
//
//
// First delete the current row and then add the new row.
// If the "key" of this row is different from the one already
// in the table, the new row may end up in a different bucket
// than the original.
//
PROPVARIANT varWid;
varWid.lVal = (LONG) obj.WorkId();
varWid.vt = VT_I4;
tbDebugOut(( DEB_BOOKMARK, "CLargeTable - Delete And ReAdd WorkId 0x%X\n",
varWid.lVal ));
WORKID widNext;
CI_TBL_CHAPT chapt;
//
// Delete the row and then add a new one.
//
pSegment->RemoveRow( varWid, widNext, chapt );
_LokRemoveCategorizedRow( chapt,
obj.WorkId(),
widNext,
pSegment );
}
else
{
pSegment = _segListMgr.GetCachedPutRowSeg();
}
CTableRowPutter rowPutter( *this, obj );
pSegment = rowPutter.LokFindSegToInsert( pSegment );
Win4Assert( 0 != pSegment );
//
// If the current segment is getting full, we should either split it
// or create a new one.
//
if ( pSegment->IsGettingFull() )
pSegment = rowPutter.LokSplitOrAddSegment( pSegment );
Win4Assert( 0 != pSegment );
Win4Assert( pSegment->GetLowestKey().IsInitialized() );
Win4Assert( pSegment->GetHighestKey().IsInitialized() );
BOOL fRowThrownAway = FALSE;
// Check for Row limit...
ULONG cRowLimit = FirstRows();
tbDebugOut(( DEB_ITRACE, "CLargeTable::PutRow: FirstRows is %d, MaxRows is %d\n", FirstRows(), MaxRows() ));
BOOL fFirstRows = cRowLimit > 0;
if ( !fFirstRows )
cRowLimit = MaxRows();
tbDebugOut(( DEB_ITRACE, "CLargeTable::PutRow: RowCount() is %d\n", RowCount() ));
tbDebugOut(( DEB_ITRACE, "CLargeTable::PutRow: cRowLimit is %d\n", cRowLimit ));
if ( 0 == cRowLimit || RowCount() < cRowLimit )
{
pSegment->PutRow( obj, _currRow.GetReference() );
}
else
{
// We are here. Therefore it means that:
// There is a maxrow limit set AND rowcount >= maxrows AND
// we have at least one segment which has at least one row...
// Note: The special case of sort by rank descending is handled
// by CQAsyncExecute::Resolve in which case we only get rows less
// than equal to MaxRows (in case MaxRows is defined)
if ( !fFirstRows )
{
if ( !_fSortDefined )
{
// Since not sort order is defined, we can stop processing of
// rows here, since we have all the data that we need
_fNoMoreData = fRowThrownAway = TRUE;
}
else
{
_currRow->MakeReady();
// There is a sort defined. So we need to process all the
// rows and put the best results in the maxrow rows
CTableSegment* pLastSegment = _segListMgr.GetList().GetLast();
Win4Assert( pLastSegment );
// Now we need to make sure that the last segment is a window
// and it has at least one row in it. This is done because
// a bucket does not support the kind of operations that
// we are planning to do here on...
while ( !pLastSegment->IsWindow() || 0 == pLastSegment->RowCount() )
{
if ( 0 == pLastSegment->RowCount() )
{
// delete this segment
_segListMgr.RemoveFromList( pLastSegment );
delete pLastSegment;
pLastSegment = _segListMgr.GetList().GetLast();
Win4Assert( pLastSegment );
continue;
}
if ( !pLastSegment->IsWindow() )
{
// Convert it to a window
Win4Assert( pLastSegment->IsBucket() );
obj.Quiesce();
XPtr<CTableBucket> xBktToExpand( (CTableBucket*)pLastSegment );
CDoubleTableSegIter iter( pLastSegment );
_LokReplaceWithEmptyWindow( iter );
_NoLokBucketToWindows( xBktToExpand, 0, FALSE, FALSE );
pLastSegment = _segListMgr.GetList().GetLast();
Win4Assert( pLastSegment );
// pSegment may no longer exist -- look it up again
CTableRowPutter rp( *this, obj );
pSegment = rp.LokFindSegToInsert( 0 );
Win4Assert( 0 != pSegment );
}
}
if ( ( pLastSegment == pSegment ) &&
( _keyCompare->Compare( _currRow.GetReference(),
pSegment->GetHighestKey() ) > 0 ) )
{
// Since the current row is worse than the our worst row,
// we can throw it away
fRowThrownAway = TRUE;
// NEWFEATURE: update counter of thrown rows
}
else
{
// CurrRow is better than (at least) our worst row
// Delete the last row in the last segment and insert
// the new row. This would keep RowCount == MaxRows
PROPVARIANT varWid;
varWid.lVal = (LONG) ((CTableWindow*)pLastSegment)->
_GetLastWorkId();
Win4Assert( widInvalid != varWid.lVal );
varWid.vt = VT_I4;
WORKID widNext;
CI_TBL_CHAPT chapt;
pLastSegment->RemoveRow( varWid, widNext, chapt );
_LokRemoveCategorizedRow( chapt,
varWid.lVal,
widNext,
pLastSegment );
// Insert the new row
pSegment->PutRow( obj, _currRow.GetReference() );
if ( 0 == pLastSegment->RowCount() )
{
// remove this segment
_segListMgr.RemoveFromList( pLastSegment );
delete pLastSegment;
}
}
}
}
}
if ( !fRowThrownAway )
{
_segListMgr.SetCachedPutRowSeg( pSegment );
if ( rowPutter.LokIsNewWindowCreated() &&
( ! _fRankVectorBound ) &&
( ! IsCategorized() ) &&
( _fUniqueWorkid ) ) // don't bucketize ::_noindex_:: catalogs
{
_LokConvertWindowsToBucket();
}
_bitRefresh = 1;
LokCompleteAsyncNotification();
}
}
CATCH( CException, e )
{
if ( e.GetErrorCode() != STATUS_FILE_DELETED )
{
RETHROW();
}
}
END_CATCH
return _fProgressNeeded;
} //PutRow
//+---------------------------------------------------------------------------
//
// Member: CLargeTable::_LokDeferPutRow
//
// Synopsis:
//
// Arguments: [wid] -
//
// Returns:
//
// History: 8-01-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
inline
void CLargeTable::_LokDeferPutRow(
WORKID wid,
CRetriever & obj )
{
Win4Assert( 0 != _pSortSet );
if ( 0 == _pDeferredRows )
{
_pDeferredRows = new CTableBucket( *_pSortSet,
_keyCompare.GetReference(),
_MasterColumnSet,
_AllocSegId() );
}
PROPVARIANT vRank;
ULONG cbRank = sizeof vRank;
obj.GetPropertyValue( pidRank, &vRank, &cbRank );
Win4Assert( VT_I4 == vRank.vt );
PROPVARIANT vHitCount;
ULONG cbHitCount = sizeof vHitCount;
obj.GetPropertyValue( pidHitCount, &vHitCount, &cbHitCount );
Win4Assert( VT_I4 == vHitCount.vt );
_pDeferredRows->_AddWorkId( wid,
vRank.lVal,
vHitCount.lVal );
}
//+---------------------------------------------------------------------------
//
// Member: CLargeTable::_LokIsPutRowDeferred
//
// Synopsis: If the workid given is being watched and the client knows
// about its existence, then we must defer the addition of
// this row until later.
//
// Arguments: [wid] -
//
// Returns:
//
// History: 8-01-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL CLargeTable::_LokIsPutRowDeferred( WORKID widRow, CRetriever &obj )
{
Win4Assert( _LokIsWatched() );
PROPVARIANT varWid;
varWid.lVal = (LONG) widRow;
varWid.vt = VT_I4;
WORKID widNext;
BOOL fDeferred = FALSE;
for ( CFwdTableSegIter iter(_segList); !_segList.AtEnd(iter);
_segList.Advance(iter) )
{
CTableSegment * pSegment = iter.GetSegment();
WORKID widNext;
CI_TBL_CHAPT chapt;
//
// NEWFEATURE: (windowed notifications)
// This is not correct. We should remove it only if soft
// deletions are being done on a window. If it is a hard delete,
// we must wait until a refresh is called. May need a different
// data structure for the case of watch all - a bucket will not
// suffice.
//
if ( pSegment->RemoveRow(varWid, widNext, chapt) )
{
_LokRemoveCategorizedRow( chapt,
widRow,
widNext,
pSegment );
if ( pSegment->IsWindow() )
{
CTableWindow * pWindow = iter.GetWindow();
if ( pWindow->IsPendingDelete( widRow ) )
{
_LokDeferPutRow( widRow, obj );
fDeferred = TRUE;
}
}
break;
}
}
return fDeferred;
}
//+---------------------------------------------------------------------------
//
// Member: CLargeTable::_LokRemoveIfDeferred
//
// Synopsis:
//
// Arguments: [wid] -
//
// Returns:
//
// History: 8-01-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL CLargeTable::_LokRemoveIfDeferred( WORKID wid )
{
BOOL fRemoved = FALSE;
if ( 0 != _pDeferredRows )
{
PROPVARIANT varWid;
varWid.lVal = (LONG) wid;
varWid.vt = VT_I4;
WORKID widNext;
CI_TBL_CHAPT chapt;
fRemoved = _pDeferredRows->RemoveRow( varWid, widNext, chapt );
}
return fRemoved;
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::_LokCheckQueryStatus, private
//
// Synopsis: Fail a request if the query has encountered an error.
//
// Arguments: - NONE -
//
// Returns: Nothing, throws E_FAIL on error.
//
//--------------------------------------------------------------------------
void CLargeTable::_LokCheckQueryStatus( )
{
if (QUERY_FILL_STATUS( Status() ) == STAT_ERROR)
{
NTSTATUS sc = GetStatusError();
Win4Assert( sc != STATUS_SUCCESS );
tbDebugOut(( DEB_WARN,
"Bigtable 0x%x Query failed, sc = %x\n",
this, sc));
if (sc == STATUS_SUCCESS)
sc = E_FAIL;
THROW( CException( sc ));
}
else if ( _fAbort )
{
THROW( CException( STATUS_TOO_LATE ) );
}
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::GetRows, public
//
// Synopsis: Retrieve row data from a large table.
//
// Arguments: [widStart] - WORKID identifying first row to be
// transferred. If WORKID_TBLFIRST is
// used, the transfer will start at the first
// row in the segment.
// [chapt] - Chapter from which to fetch rows (if chaptered)
// [rOutColumns] - a CTableColumnSet that describes the
// output format of the data table.
// [rGetParams] - an CGetRowsParams structure which
// describes how many rows are to be fetched and
// other parameters of the operation.
// [rwidLastRowTransferred] - on return, the work ID of
// the last row to be transferred from this table.
// Can be used to initialize widStart on next call.
//
// Returns: SCODE - status of the operation. DB_S_ENDOFROWSET if
// widStart is WORKID_TBLAFTERLAST at start of
// transfer, or if rwidLastRowTransferred is the
// last row in the segment at the end of the transfer.
//
// DB_S_BUFFERTOOSMALL is returned if the available
// space in the out-of-line data was exhausted during
// the transfer.
//
// Notes: To transfer successive rows, as in GetNextRows, the
// rwidLastRowTransferred must be advanced by one prior
// to the next transfer.
//
//--------------------------------------------------------------------------
SCODE CLargeTable::GetRows(
HWATCHREGION hRegion,
WORKID widStart,
CI_TBL_CHAPT chapt,
CTableColumnSet const & rOutColumns,
CGetRowsParams & rGetParams,
WORKID & rwidLastRowTransferred
)
{
return GetRowsAt( hRegion, widStart, chapt, 0, rOutColumns,
rGetParams, rwidLastRowTransferred );
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::RestartPosition, public
//
// Synopsis: Set next fetch position for the chapter to the start
//
// Arguments: [chapt] - Chapter from which to fetch rows (if chaptered)
//
// Returns: SCODE - status of the operation.
//
//--------------------------------------------------------------------------
void CLargeTable::RestartPosition(
CI_TBL_CHAPT chapt)
{
SetCurrentPosition( chapt, WORKID_TBLBEFOREFIRST );
CTableSource::RestartPosition( chapt );
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::RowCount, public
//
// Synopsis: Return the total row count in the table
//
// Returns: ULONG - row count aggregated over all segments in the
// table.
//
//--------------------------------------------------------------------------
DBCOUNTITEM CLargeTable::RowCount()
{
CLock lock(_mutex);
_LokCheckQueryStatus();
return _LokRowCount();
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::_LokRowCount, public
//
// Synopsis: Return the total row count in the table
//
// Returns: ULONG - row count aggregated over all segments in the
// table.
//
//--------------------------------------------------------------------------
DBCOUNTITEM CLargeTable::_LokRowCount()
{
DBCOUNTITEM cRowsTotal = 0;
for ( CFwdTableSegIter iter(_segList); !_segList.AtEnd(iter); _segList.Advance(iter) )
{
cRowsTotal += iter.GetSegment()->RowCount();
}
return cRowsTotal;
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::RatioFinished
//
// Synopsis: Return query progress
//
// Arguments: [ulDeneominator] - on return, denominator of fraction
// [ulNumerator] - on return, numerator of fraction
// [cRows] - on return, number of rows in table
//
// Notes: For the fQuick case, we could try doing a quick
// synchronization with the CAsyncExecute to compute
// a good value for the ratio, but the implementation
// below is fine for the Gibraltar query since no callers
// will use the ratio anyway.
//
// A sketch of the code needed to do the quick synchronization
// is below:
// BOOL CAsyncExecute::QuickRF( ULONG &ulDen, ULONG &ulNum )
// {
// CLock lock(_mutex);
// if (_fRunning)
// return FALSE;
// else
// {
// _pCurResolve->RatioFinished( ulDen, ulNum );
// return TRUE;
// }
// }
//
//
// History: Mar-20-1995 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::RatioFinished (
DBCOUNTITEM& ulDenominator,
DBCOUNTITEM& ulNumerator,
DBCOUNTITEM& cRows )
{
CLock lock(_mutex);
_LokCheckQueryStatus();
if (_fQuiescent)
{
cRows = _LokRowCount();
ulDenominator = ulNumerator = 100;
return;
}
_fProgressNeeded = TRUE;
ulDenominator = _ulProgressDenom;
ulNumerator = _ulProgressNum;
cRows = _LokRowCount();
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::ProgressDone, public
//
// Synopsis: Sets the progress indicators and wakes up
// the client
//
// Arguments: [ulDenominator]
// [ulNumerator]
//
// History: Mar-21-95 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::ProgressDone (ULONG ulDenominator, ULONG ulNumerator)
{
tbDebugOut(( DEB_ITRACE, "CLargeTable reporting progress %ld / %ld\n",
ulNumerator, ulDenominator ));
CLock lock(_mutex);
_fProgressNeeded = FALSE;
_ulProgressDenom = ulDenominator;
_ulProgressNum = ulNumerator;
}
void CLargeTable::Quiesce ()
{
TRY
{
CLock lock(_mutex);
tbDebugOut(( DEB_NOTIFY, "CLargeTable reached quiescent state\n" ));
Win4Assert( QUERY_FILL_STATUS( Status() ) == STAT_DONE ||
QUERY_FILL_STATUS( Status() ) == STAT_ERROR );
_fProgressNeeded = FALSE;
_fQuiescent = TRUE;
_ulProgressDenom = 100;
_ulProgressNum = 100;
// don't tell the client we quiesced more than once
tbDebugOut(( DEB_ITRACE, "CLargeTable::Quiesce: _bitQuiesced is %d\n", _bitQuiesced ));
if ( 0 == _bitQuiesced )
{
_bitChangeQuiesced = 1;
_bitQuiesced = 1;
LokCompleteAsyncNotification();
}
// inform the client once that we're complete
if ( 0 != _pQuiesce )
{
_pQuiesce->QueryQuiesced( TRUE, _scStatus );
_pQuiesce = 0;
}
}
CATCH( CException, e )
{
// ignore the exception; it may be in a unwind path
}
END_CATCH;
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::GetApproximatePosition, public
//
// Synopsis: Return the approximate current position as a fraction
//
// Arguments: [chapt] - chapter
// [bmk] - bookmark
// [pulNumerator] - on return, numerator of fraction
// [pulRowCount] - on return, denominator of fraction (row count)
//
// Returns: SCODE - the status of the operation.
//
// Notes: The denominator of the fraction is the approximate
// row count in the table or chapter.
//
//--------------------------------------------------------------------------
SCODE
CLargeTable::GetApproximatePosition(
CI_TBL_CHAPT chapt,
CI_TBL_BMK bmk,
DBCOUNTITEM * pulNumerator,
DBCOUNTITEM * pulRowCount
)
{
CLock lock(_mutex);
_LokCheckQueryStatus();
if (bmk == widInvalid)
return DB_E_BADBOOKMARK;
Win4Assert (bmk != WORKID_TBLBEFOREFIRST && bmk != WORKID_TBLAFTERLAST);
DBCOUNTITEM iBmkPosition = ULONG_MAX;
DBCOUNTITEM cRows = 0;
if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
{
cRows = GetCategorizer()->GetRowCount( chapt );
if ( WORKID_TBLFIRST == bmk )
{
iBmkPosition = 1;
if (cRows == 0)
iBmkPosition = 0;
}
else if ( WORKID_TBLLAST == bmk )
{
iBmkPosition = cRows;
}
else
{
WORKID widFirst = GetCategorizer()->GetFirstWorkid( chapt );
CFwdTableSegIter iter( _segList );
DBCOUNTITEM cChaptRows = 0;
BOOL fFoundFirstYet = FALSE;
while ( !_segList.AtEnd(iter) )
{
ULONG iRow = 0;
CTableSegment * pSegment = iter.GetSegment();
if ( pSegment->IsWindow() )
{
CTableWindow * pWindow = iter.GetWindow();
ULONG iFirstRow;
if ( !fFoundFirstYet &&
pWindow->RowOffset( widFirst, iFirstRow ) )
{
if ( pWindow->RowOffset(bmk, iRow) )
{
iBmkPosition = iRow - iFirstRow + 1;
break;
}
else
{
cChaptRows = pSegment->RowCount() - iFirstRow;
fFoundFirstYet = TRUE;
}
}
else if ( pWindow->RowOffset(bmk, iRow) )
{
// We can't have set the numerator previously.
Win4Assert(iBmkPosition == ULONG_MAX);
iBmkPosition = cChaptRows + iRow + 1;
break;
}
else if ( fFoundFirstYet )
{
cChaptRows += pSegment->RowCount();
}
}
else
{
// The chapter is in a bucket. All rows in the
// bucket are in the chapter, and the chapter
// spans no other segments.
CTableBucket * pBucket = iter.GetBucket();
if ( pBucket->IsRowInSegment(bmk) )
{
Win4Assert( iBmkPosition == ULONG_MAX );
if ( pBucket->RowOffset(bmk, iRow) )
{
iBmkPosition = iRow + 1;
}
else
{
iBmkPosition = ((ULONG)pBucket->RowCount() + 1)/2;
}
}
}
_segList.Advance(iter);
}
}
}
else
{
if (bmk == WORKID_TBLFIRST)
{
iBmkPosition = 1;
cRows = RowCount();
if (cRows == 0)
iBmkPosition = 0;
}
else if (bmk == WORKID_TBLLAST)
{
cRows = RowCount();
iBmkPosition = cRows;
}
else
{
//
// Iterate over all table segments prior to the table seg.
// in which the bookmark occurs, adding their row counts to
// iBmkPosition. Accumulate the total row count at the same
// time.
//
CFwdTableSegIter iter( _segList );
while ( !_segList.AtEnd(iter) )
{
ULONG iRow = 0;
CTableSegment * pSegment = iter.GetSegment();
if ( pSegment->IsWindow() )
{
CTableWindow * pWindow = iter.GetWindow();
if ( pWindow->RowOffset(bmk, iRow) )
{
// We can't have set the numerator previously.
Win4Assert(iBmkPosition == ULONG_MAX);
iBmkPosition = cRows + iRow + 1;
}
}
else
{
CTableBucket * pBucket = iter.GetBucket();
if ( pBucket->IsRowInSegment(bmk) )
{
Win4Assert( iBmkPosition == ULONG_MAX );
iBmkPosition = cRows;
if ( pBucket->RowOffset(bmk, iRow) )
{
iBmkPosition += iRow + 1;
}
else
{
iBmkPosition += ((ULONG)pBucket->RowCount() + 1)/2;
}
}
}
cRows += pSegment->RowCount();
_segList.Advance(iter);
}
}
}
if (iBmkPosition == ULONG_MAX)
return DB_E_BADBOOKMARK;
Win4Assert(iBmkPosition <= cRows);
*pulNumerator = iBmkPosition;
*pulRowCount = cRows;
return S_OK;
} //GetApproximagePosition
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::IsColumnInTable, public
//
// Synopsis: Check whether some column can be added to the table.
// Used in support of CQuery::SetBindings; added columns
// may only refeerence columns which already exist in the
// table.
//
// Arguments: [PropId] - the property ID to be added to the table.
//
// Returns: BOOL - TRUE if it's okay to add the column. False
// otherwise.
//
// Notes:
//
//--------------------------------------------------------------------------
BOOL
CLargeTable::IsColumnInTable(
PROPID PropId
) {
CLock lock(_mutex);
//
// See if the column already exists in the master column set
//
if ( _MasterColumnSet.Find( PropId ) != 0 ) {
return TRUE;
} else {
return FALSE;
}
}
#if 0
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::_LokLocateTableSegment, private
//
// Synopsis: Position a table segment iterator to the table segment
// in which some work ID is found.
//
// Arguments: [rIter] - An iterator over table segments
// [chapt] - Chapter in which to search for row
// [wid] - value to be searched for
//
// Returns: BOOL - TRUE if the work ID was found, FALSE if not.
//
// Notes:
//
//--------------------------------------------------------------------------
WORKID
CLargeTable::_LokLocateTableSegment(
CDoubleTableSegIter& rIter,
CI_TBL_CHAPT chapt,
WORKID wid
)
{
Win4Assert( !_segList.AtEnd(rIter) );
if ( IsSpecialWid( wid ) )
{
if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
{
if ( WORKID_TBLFIRST == wid ||
WORKID_TBLBEFOREFIRST == wid )
{
wid = GetCategorizer()->GetFirstWorkid( chapt );
}
else
{
// Heuristic: assume last row of [chapt] is in same window
// as first row of [ next chapt ]
wid = GetCategorizer()->GetFirstWorkidOfNextCategory( chapt );
if ( widInvalid == wid )
{
// chapt was the last chapter -- get the last segment
while ( !_segList.IsLast(rIter) )
_segList.Advance(rIter);
return TRUE;
}
else
{
// check if this row (the first of the next chapter) is
// the first in the segment. If so, back up to the prev
// segment.
Win4Assert( _segList.IsFirst(rIter) );
while ( !_segList.AtEnd(rIter) )
{
if ( rIter.GetSegment()->IsRowInSegment(wid) )
{
ULONG iRow;
CTableWindow * pWindow = rIter.GetWindow();
pWindow->RowOffset( wid, iRow );
if ( 0 == iRow )
{
// uh, oh. Back up a segment. The first row of
// the next category is the first row in the seg,
// so the last row of the categ must be in the
// prev segment.
Win4Assert( !_segList.IsFirst(rIter) );
_segList.BackUp(rIter);
}
return TRUE;
}
_segList.Advance(rIter);
}
tbDebugOut((DEB_WARN, "Got confused looking for %x\n", wid));
return FALSE;
}
}
}
else
{
//
// For the special cases of Beginning and End, just position to the
// correct end.
//
if (wid == WORKID_TBLBEFOREFIRST || wid == WORKID_TBLFIRST)
{
while ( !_segList.IsFirst(rIter) )
_segList.BackUp(rIter);
}
else
{
Win4Assert( wid == WORKID_TBLLAST ||
wid == WORKID_TBLAFTERLAST );
while ( !_segList.IsLast(rIter) )
_segList.Advance(rIter);
}
return TRUE;
}
}
//
// Locate the appropriate segment
// NOTE: we assume we start with a fresh iterator, so just go forward
//
Win4Assert( _segList.IsFirst(rIter) );
while ( !_segList.AtEnd(rIter) )
{
if ( rIter.GetSegment()->IsRowInSegment(wid) )
{
return TRUE;
}
_segList.Advance(rIter);
}
tbDebugOut((DEB_WARN, "Work ID %x not found in table\n", wid));
return FALSE;
}
#endif
//
// Methods which call through to the constituent table segments.
// These will in general just select one segment to call, or
// iterate over all segments.
//
//+---------------------------------------------------------------------------
//
// Function: IsRowInSegment
//
// Synopsis:
//
// Arguments: [wid] - Workid of row to be found
//
// Returns: BOOL - TRUE if row is found, FALSE otherwise
//
// History: 3-24-95 srikants Created
//
//----------------------------------------------------------------------------
BOOL CLargeTable::IsRowInSegment( WORKID wid )
{
CLock lock(_mutex);
return (_LokFindTableSegment(wid) != 0);
}
//+---------------------------------------------------------------------------
//
// Function: SortOrder
//
// Synopsis:
//
// Returns:
//
// History: 3-24-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
CSortSet const & CLargeTable::SortOrder()
{
//CLock lock(_mutex);
return( *_pSortSet );
}
//+---------------------------------------------------------------------------
//
// Function: RemoveRow
//
// Synopsis:
//
// Arguments: [varUnique] -
//
// Returns:
//
// History: 3-24-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CLargeTable::RemoveRow( PROPVARIANT const & varUnique )
{
CLock lock(_mutex);
if ( !_LokIsWatched() )
{
//
// Do not process deletions unless the table is watched.
//
return;
}
WORKID widRow = widInvalid;
if ( _fUniqueWorkid )
widRow = varUnique.lVal;
else
{
CColumnMasterDesc* pMastCol = _MasterColumnSet.Find( pidPath );
Win4Assert(0 != pMastCol && pMastCol->IsCompressedCol());
//
// Convert the filename to a wid before deletion.
//
BOOL fFound = pMastCol->GetCompressor()->FindData( &varUnique,
widRow );
// See if the delete wasn't in the table to begin with
if ( !fFound )
return;
}
if ( !_LokRemoveIfDeferred( widRow ) )
{
//
// This row was not a deferred update. We must iterate through
// the segments and remove it.
//
PROPVARIANT varWid;
varWid.lVal = (LONG) widRow;
varWid.vt = VT_I4;
for ( CFwdTableSegIter iter(_segList); !_segList.AtEnd(iter);
_segList.Advance(iter) )
{
CTableSegment * pSegment = iter.GetSegment();
WORKID widNext;
CI_TBL_CHAPT chapt;
// No Hard Delete.
if ( pSegment->RemoveRow( varWid, widNext, chapt ) )
{
_LokRemoveCategorizedRow( chapt,
widRow,
widNext,
pSegment );
break;
}
}
}
_bitRefresh = 1;
LokCompleteAsyncNotification();
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::NeedToNotifyReset, private
//
// Synopsis: Checks if there is a need to notify the client
// Resets the changeQuiesced bit if needed
//
// Arguments: [changeType] -- out, what change type
//
// History: Arp-4-95 BartoszM Created
//
//--------------------------------------------------------------------------
BOOL CLargeTable::NeedToNotifyReset (DBWATCHNOTIFY& changeType)
{
CLock lock (_mutex);
return LokNeedToNotifyReset(changeType);
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::LokNeedToNotifyReset, private
//
// Synopsis: Checks if there is a need to notify the client
// Resets the changeQuiesced bit if needed
//
// Arguments: [changeType] -- out, what change type
//
// History: Arp-4-95 BartoszM Created
//
//--------------------------------------------------------------------------
BOOL CLargeTable::LokNeedToNotifyReset(DBWATCHNOTIFY& changeType)
{
if (_bitRefresh )
{
if (!_bitClientNotified)
{
changeType = DBWATCHNOTIFY_ROWSCHANGED;
return TRUE;
}
}
else // no need to run changes
{
if (_bitChangeQuiesced)
{
changeType = (_bitQuiesced)? DBWATCHNOTIFY_QUERYDONE: DBWATCHNOTIFY_QUERYREEXECUTED;
tbDebugOut(( DEB_NOTIFY, "changetype set to %d\n", changeType ));
//
// Reset the bit!
//
_bitChangeQuiesced = 0;
return TRUE;
}
}
return FALSE;
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::NeedToNotify, private
//
// Synopsis: Checks if there is a need to notify the client
//
// History: 29-Aug-95 dlee Created
//
//--------------------------------------------------------------------------
BOOL CLargeTable::NeedToNotify()
{
CLock lock (_mutex);
return LokNeedToNotify();
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::LokNeedToNotify, private
//
// Synopsis: Checks if there is a need to notify the client
//
// History: 29-Aug-95 dlee Created
//
//--------------------------------------------------------------------------
BOOL CLargeTable::LokNeedToNotify()
{
if (_bitRefresh )
{
if (!_bitClientNotified)
return TRUE;
}
else if (_bitChangeQuiesced)
{
return TRUE;
}
return FALSE;
}
//+---------------------------------------------------------------------------
//
// Member: CLargeTable::GetNotifications, private
//
// Synopsis: Retrieves the notification info from the query object
// row data.
//
// Arguments: [rSync] -- notification synchronization info
// [rParams] -- notification data info
//
// Returns: SCODE
//
// History: 10-24-94 dlee created
//
//----------------------------------------------------------------------------
SCODE CLargeTable::GetNotifications(
CNotificationSync & rSync,
DBWATCHNOTIFY & changeType )
{
tbDebugOut (( DEB_NOTIFY, "lt: GetNotifications\n" ));
{
CLock lock(_mutex);
_bitNotifyEnabled = 1;
// don't fail if the query failed -- report the notification that
// the query completed.
// _LokCheckQueryStatus();
}
SCODE sc = S_OK;
{
CLock lock(_mutex);
Win4Assert( 0 == _pRequestServer );
Win4Assert( 0 == _hNotifyEvent );
if ( LokNeedToNotifyReset( changeType ) )
{
_bitClientNotified = 1;
tbDebugOut (( DEB_NOTIFY, "complete in getnotify: %d\n",
changeType ));
return S_OK;
}
else
{
if ( rSync.IsSvcMode() )
{
Win4Assert( 0 == _pRequestServer );
_pRequestServer = rSync.GetRequestServer();
Win4Assert( 0 != _pRequestServer );
tbDebugOut (( DEB_NOTIFY, "getnotify returning pending\n" ));
return STATUS_PENDING;
}
// Block on an event until notifications
// arrive (or the table is going away). If notifications
// exist, grab them. Also block on the notification cancel
// event and report if that was received.
Win4Assert( 0 == _hNotifyEvent );
_hNotifyEvent = CreateEvent( 0, TRUE, FALSE, 0 );
if ( 0 == _hNotifyEvent )
{
tbDebugOut(( DEB_ERROR, "Create event returned 0d\n",
GetLastError() ));
THROW( CException() );
}
}
}
HANDLE aEvents[2];
aEvents[0] = _hNotifyEvent;
aEvents[1] = rSync.GetCancelEvent();
ULONG wait = WaitForMultipleObjects( 2,
aEvents,
FALSE,
INFINITE );
CloseHandle( _hNotifyEvent );
_hNotifyEvent = 0;
if ( STATUS_WAIT_0 == wait )
{
CLock lock(_mutex);
changeType = _changeType;
}
else if ( STATUS_WAIT_1 == wait )
{
sc = STATUS_CANCELLED;
}
else
{
Win4Assert(!"Unexpected return from WaitForMultipleObjects()");
}
return sc;
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::CreateWatchRegion
//
// Synopsis: Creates a new watch region
//
// Arguments: [mode] -- initial mode
// [phRegion] -- (out) region handle
//
// History: Jun-16-95 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::CreateWatchRegion (ULONG mode, HWATCHREGION* phRegion)
{
CLock lock( _mutex );
_bitIsWatched = 1;
*phRegion = _watchList.NewRegion (mode);
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::ChangeWatchMode
//
// Synopsis: Changes watch mode of a region
//
// Arguments:
// [hRegion] -- region handle
// [mode] -- new mode
//
// History: Jun-16-95 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::ChangeWatchMode ( HWATCHREGION hRegion, ULONG mode)
{
CLock lock( _mutex );
_watchList.ChangeMode (hRegion, mode);
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::GetWatchRegionInfo
//
// Synopsis: Retrieves watch region information
//
// Arguments: [hRegion] -- region handle
// [pChapter] -- (out) chapter
// [pBookmark] -- (out) bookmark
// [pcRows] -- (out) size in rows
//
// History: Jun-16-95 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::GetWatchRegionInfo ( HWATCHREGION hRegion,
CI_TBL_CHAPT* pChapter,
CI_TBL_BMK* pBookmark,
DBROWCOUNT * pcRows)
{
CLock lock( _mutex );
_watchList.GetInfo (hRegion, pChapter, pBookmark, (DBCOUNTITEM *)pcRows);
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::DeleteWatchRegion
//
// Synopsis: Delete watch region
//
// Arguments: [hRegion] -- region handle
//
// History: Jun-16-95 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::DeleteWatchRegion (HWATCHREGION hRegion)
{
CLock lock( _mutex );
_watchList.DeleteRegion (hRegion);
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::ShrinkWatchRegion
//
// Synopsis: Shrinks watch region
//
// Arguments: [hRegion] -- region handle
// [chapter] -- new chapter
// [bookmark] -- new bookmark
// [cRows] -- new size in rows
//
// History: Jun-16-95 BartoszM Created
//
//--------------------------------------------------------------------------
void CLargeTable::ShrinkWatchRegion (HWATCHREGION hRegion,
CI_TBL_CHAPT chapter,
CI_TBL_BMK bookmark,
LONG cRows )
{
CLock lock( _mutex );
_watchList.ShrinkRegion (hRegion, chapter, bookmark, cRows);
#if CIDBG==1
CWatchRegion * pRegion = _watchList.GetRegion(hRegion);
_watchList.CheckRegionConsistency( pRegion );
#endif //CIDBG==1
}
//+-------------------------------------------------------------------------
//
// Member: CLargeTable::Refresh
//
// Synopsis: Stub implementation
//
// Arguments: [] --
//
// History: Apr-4-95 BartoszM Created
// Jul-27-95 BartoszM Implemented (with no change script)
//
//--------------------------------------------------------------------------
void CLargeTable::Refresh()
{
CDynStack<CTableBucket> xBktToConvert(0); // buckets to expand at our leisure
{
CLock lock (_mutex);
_LokCheckQueryStatus();
_bitClientNotified = 0;
_bitRefresh = 0;
// Update the bookmarks before refreshing windows.
// We need to know where they will be migrating
// in case rows were deleted.
// Remove watch regions from windows
for (CWatchIter iterWatch1 (_watchList);
!_watchList.AtEnd(iterWatch1);
_watchList.Advance(iterWatch1))
{
CWatchRegion* pRegion = iterWatch1.Get();
if (pRegion->IsInit())
{
CTableWindow * pWindow = (CTableWindow *) pRegion->Segment();
CI_TBL_BMK bookmark = _FindNearestDynamicBmk (
pWindow,
pRegion->Chapter(),
pRegion->Bookmark());
pRegion->Set( pRegion->Chapter(),
bookmark,
pRegion->RowCount() );
_watchList.ShrinkRegionToZero (pRegion->Handle());
}
}
// Recreate new watch regions in windows
// Find starting segments using bookmarks
for (CWatchIter iterWatch3 (_watchList);
!_watchList.AtEnd(iterWatch3);
_watchList.Advance(iterWatch3))
{
CWatchRegion* pRegion = iterWatch3.Get();
if ( pRegion->RowCount() > 0 )
{
Win4Assert( widInvalid != pRegion->Bookmark() );
CTableSegment* pSegment = _LokFindTableSegment(pRegion->Bookmark());
pRegion->SetSegment( pSegment );
if ( 0 != pSegment )
{
LokStretchWatchRegion (pRegion, xBktToConvert);
_watchList. BuildRegion (
pRegion->Handle(),
pSegment,
pRegion->Chapter(),
pRegion->Bookmark(),
pRegion->RowCount() );
}
#if CIDBG==1
_watchList.CheckRegionConsistency( pRegion );
#endif // CIDBG==1
}
}
//
// If there are any deferred rows that were not added during the
// normal "PutRow", we should expand them now.
//
if ( 0 != _pDeferredRows )
{
xBktToConvert.Push(_pDeferredRows);
_pDeferredRows = 0;
}
if (_bitChangeQuiesced)
LokCompleteAsyncNotification();
}
if ( xBktToConvert.Count() > 0 )
{
// Schedule buckets for asynchronous expansion
_NoLokBucketToWindows( xBktToConvert, widInvalid, TRUE, FALSE );
}
} //Refresh
void CLargeTable::LokStretchWatchRegion ( CWatchRegion* pRegion,
CDynStack<CTableBucket>& xBktToConvert)
{
CTableWindow* pFirstWindow = (CTableWindow*) pRegion->Segment();
Win4Assert (pFirstWindow->IsWindow());
//
// Compute the number of rows that can be retrieved from current window.
//
TBL_OFF dummy;
ULONG iRow;
pFirstWindow->FindBookMark(pRegion->Bookmark(), dummy, iRow );
ULONG cRowsToRetrieve = pRegion->RowCount();
ULONG cRowsFromCurrWindow = (ULONG)pFirstWindow->RowCount() - iRow;
if ( cRowsToRetrieve > cRowsFromCurrWindow )
{
//
// We still have more rows to be retrieved.
//
cRowsToRetrieve -= cRowsFromCurrWindow;
}
else
{
//
// This window can give us all the rows to be fetched.
//
return;
}
CDoubleTableSegIter iter (pRegion->Segment());
do
{
Win4Assert( !_segList.AtEnd(iter) );
_segList.Advance( iter );
if ( _segList.AtEnd(iter) )
{
break;
}
if ( iter.GetSegment()->IsBucket() )
{
CTableBucket * pBucket = _LokReplaceWithEmptyWindow(iter);
xBktToConvert.Push(pBucket);
}
else
{
CTableWindow * pWindow = iter.GetWindow();
cRowsFromCurrWindow = (ULONG)pWindow->RowCount();
if ( cRowsToRetrieve > cRowsFromCurrWindow )
{
cRowsToRetrieve -= cRowsFromCurrWindow;
}
else
{
cRowsToRetrieve = 0;
}
}
}
while ( cRowsToRetrieve > 0 );
}
//+---------------------------------------------------------------------------
//
// Member: CLargeTable::CancelAsyncNotification
//
// Synopsis: signals the notification event
//
// History: 10-24-94 dlee created
//
// Notes: can only be called from the destructor
//
//----------------------------------------------------------------------------
void CLargeTable::CancelAsyncNotification()
{
if ( 0 != _pRequestServer )
{
//_pRequestServer->CompleteNotification( 0 );
_pRequestServer = 0;
return;
}
if ( 0 != _hNotifyEvent )
SetEvent( _hNotifyEvent );
}
//+---------------------------------------------------------------------------
//
// Member: CLargeTable::LokCompleteAsyncNotification
//
// Synopsis: signals the notification event
//
// History: 10-24-94 dlee created
//
//----------------------------------------------------------------------------
void CLargeTable::LokCompleteAsyncNotification()
{
if (_bitClientNotified && !_bitChangeQuiesced)
return; // no need to keep pinging the client
Win4Assert( ! ( _pRequestServer && _hNotifyEvent ) );
if ( 0 != _pRequestServer )
{
if ( LokNeedToNotifyReset( _changeType ) )
{
if ( DBWATCHNOTIFY_ROWSCHANGED == _changeType )
_bitClientNotified = 1;
tbDebugOut (( DEB_NOTIFY, "complete in lcan: %d\n",
_changeType ));
_pRequestServer->CompleteNotification( _changeType );
_pRequestServer = 0;
}
return;
}
if ( 0 != _hNotifyEvent )
{
if ( LokNeedToNotifyReset( _changeType ) )
{
if ( DBWATCHNOTIFY_ROWSCHANGED == _changeType )
_bitClientNotified = 1;
SetEvent( _hNotifyEvent );
// event will be released / zeroed by notify thread.
}
}
} //LokCompleteAsyncNotification
//+---------------------------------------------------------------------------
//
// Function: _LokConvertToBucket
//
// Synopsis: Converts the given window into a bucket and replace the
// window with a bucket.
//
// Arguments: [window] - The window to be converted into a bucket.
//
// Returns: The bucket that was created.
//
// History: 3-24-95 srikants Created
//
//----------------------------------------------------------------------------
void CLargeTable::_LokConvertToBucket( CTableWindow ** ppWindow )
{
//
// Convert the window into a bucket.
//
CTableWindow * pWindow = *ppWindow;
Win4Assert( pWindow->IsWindow() );
CBucketizeWindows bucketize( *this, *pWindow );
tbDebugOut(( DEB_WINSPLIT, "Converting 0x%X window to buckets\n",
pWindow->GetSegId() ));
bucketize. LokCreateBuckets( *_pSortSet,
_keyCompare.GetReference(),
_MasterColumnSet );
CTableSegList & bktList = bucketize.GetBucketsList();
for ( CFwdTableSegIter iter2(bktList); !bktList.AtEnd(iter2);
bktList.Advance(iter2) )
{
//
// This information is needed for doing a wid->path translation
//
CTableBucket * pBucket = iter2.GetBucket();
pBucket->SetLargeTable(this);
}
if ( 0 != bktList.GetSegmentsCount() )
{
_segListMgr.Replace( pWindow, bktList );
}
else
{
_segListMgr.RemoveFromList( pWindow );
}
*ppWindow = 0;
delete pWindow;
} //_LokConvertToBucket
//+---------------------------------------------------------------------------
//
// Function: _LokConvertWindowsToBucket
//
// Synopsis: Uses heuristics to convert some of the windows into buckets
// to reduce memory usage.
//
// History: 3-24-95 srikants Created
//
// Notes: This function needs a lot of work. For now, it just tries
// to avoid converting windows that "were recently used". It
// also tries to alternate windows and buckets if possible.
//
// A heuristic used is NOT to convert the "last" window into a
// bucket. This is because in case of content queries, the
// results are probably coming sorted and we can use this fact.
// Since new rows always go to the end, we will win by creating
// well sorted buckets when windows are converted into buckets.
//
// Also, don't convert a window to a bucket if it is < 40%
// capacity.
//
//----------------------------------------------------------------------------
void CLargeTable::_LokConvertWindowsToBucket( WORKID widToPin )
{
if ( _segList.GetWindowsCount() < cMaxWindows )
return;
unsigned cWindows = 0;
BOOL fConvert = TRUE; // used to keep alternated segments as
// windows (approx)
CBackTableSegIter iter(_segList);
while( !_segList.AtEnd(iter) )
{
CTableSegment * pSegment = iter.GetSegment();
if ( pSegment->IsWindow() )
{
cWindows++;
CTableWindow * pWindow = iter.GetWindow();
ULONG cRows = (ULONG)pWindow->RowCount();
if ( fConvert &&
cRows >= cMinRowsToBucketize &&
!pWindow->IsWatched() &&
!_segListMgr.IsRecentlyUsed(pWindow) &&
!_segList.IsLast(iter) &&
!_segList.IsFirst(iter) &&
( widInvalid == widToPin || !pWindow->IsRowInSegment( widToPin ) ) )
{
//
// The window may be deleted. So, we must backup the iterator
// before destroying the window.
//
Win4Assert( widInvalid == widToPin ||
!pWindow->IsRowInSegment( widToPin ) );
CTableWindow * pWindow = iter.GetWindow();
_segList.BackUp(iter);
_LokConvertToBucket( &pWindow );
//
// Don't convert the next segment into a bucket.
//
fConvert = FALSE;
}
else
{
_segList.BackUp(iter);
//
// We had a window which we didn't convert into a bucket.
// If the next one is a window, we can convert it.
//
fConvert = TRUE;
}
}
else
{
_segList.BackUp(iter);
}
}
Win4Assert( cWindows >= cMaxWindows );
} //_LokConvertWindowsToBucket
//+---------------------------------------------------------------------------
//
// Function: _LokReplaceWithEmptyWindow
//
// Synopsis: Replaces the given bucket with an empty window in preparation
// for bucket->window conversion.
//
// Arguments: [pBucket] - The bucket to replace
//
// History: 4-11-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
CTableBucket *
CLargeTable::_LokReplaceWithEmptyWindow( CDoubleTableSegIter & iter )
{
Win4Assert( iter.GetSegment()->IsBucket() );
CTableBucket * pBucket = iter.GetBucket();
tbDebugOut(( DEB_WINSPLIT,
"Replacing Bucket 0x%X with Window\n", pBucket->GetSegId() ));
CTableWindow * pWindow = _CreateNewWindow( pBucket->GetSegId(),
pBucket->GetLowestKey(),
pBucket->GetHighestKey());
_segListMgr.Replace( iter, pWindow );
//
// Make the newly created window preferred place to put the new
// rows in.
//
_segListMgr.SetCachedPutRowSeg( pWindow );
return pBucket;
}
//+---------------------------------------------------------------------------
//
// Function: _NoLokBucketToWindows
//
// Synopsis:
//
// Arguments: [xBktToExpand] -
// [widToPin] -
// [isWatched] -
//
// Returns:
//
// History: 7-07-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void
CLargeTable::_NoLokBucketToWindows( XPtr<CTableBucket> & xBktToExpand,
WORKID widToPin,
BOOL isWatched,
BOOL fOptimizeBucketization )
{
CAsyncBucketExploder * pBktExploder =
new CAsyncBucketExploder( *this,
xBktToExpand,
widToPin,
!_fUniqueWorkid );
//
// DO NOT add any code here. pBucket now belongs to pBktExploder and we
// should acquire it from xBktToExpand before doing anything else.
//
Win4Assert( 0 == xBktToExpand.GetPointer() );
XInterface<CAsyncBucketExploder> xRef(pBktExploder);
//
// We don't have to do an AddRef on pBktExploder because it is refcounted
// in the constructor.
//
NTSTATUS status = STATUS_SUCCESS;
//
// Lock the table
// ============================================================
//
{
CLock lock(_mutex);
_LokCheckQueryStatus();
if ( 0 != _pQExecute )
{
pBktExploder->SetQuery( _pQExecute );
}
else
{
//
// The query is being destoryed.
//
THROW( CException( STATUS_TOO_LATE ) );
}
//
// Convert any excess windows to buckets.
//
if ( fOptimizeBucketization )
_LokConvertWindowsToBucket( widToPin );
//
// Add to the bigtable's list of exploding buckets.
//
_LokAddToExplodeList( pBktExploder );
pBktExploder->SetOnLTList();
}
//
// Release the table
// ============================================================
//
if ( isWatched )
{
//
// There can be a failure (like failing to create a worker thread)
// when we add to the work queue. So, we must be able to deal with
// it.
//
TRY
{
ciFAILTEST( STATUS_NO_MEMORY );
pBktExploder->AddToWorkQueue();
}
CATCH( CException, e )
{
tbDebugOut(( DEB_ERROR,
"CLargeTable::_NoLokBucketToWindow "
"AddToWorkQueue failed with error 0X%X\n",
e.GetErrorCode() ));
SetStatus( STAT_ERROR );
_RemoveFromExplodeList( pBktExploder );
pBktExploder->Abort();
RETHROW();
}
END_CATCH
xRef.Acquire();
pBktExploder->Release();
//
// Return to the caller from here and continue fetching the
// remaining rows.
//
}
else
{
//
// No need of using another thread. Just use the callers
// thread.
//
pBktExploder->DoIt( 0 );
status = pBktExploder->GetStatus();
xRef.Acquire();
pBktExploder->Release();
}
if ( STATUS_SUCCESS != status )
{
THROW( CException(status) );
}
if (!isWatched)
{
CLock lock(_mutex);
//
// Give a notification to "kick" the notification thread after the
// bucket->window conversion.
//
LokCompleteAsyncNotification();
}
}
//+---------------------------------------------------------------------------
//
// Function: _NoLokBucketToWindows
//
// Synopsis: Converts the given buckets into a windows.
//
// Arguments: [bucketRef] - Safe stack of buckets.
//
// History: 3-24-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void
CLargeTable::_NoLokBucketToWindows( CDynStack<CTableBucket> & xSegStack,
WORKID widToPin,
BOOL isWatched,
BOOL fOptimizeBucketization )
{
//
// The bucket that must be scheduled for expansion.
//
XPtr<CTableBucket> xBktToExpand;
do
{
//
//=============================================================
// Lock the table.
//
{
CLock lock(_mutex);
CTableBucket * pBucket = (CTableBucket *) xSegStack.Pop();
Win4Assert( 0 == xBktToExpand.GetPointer() );
xBktToExpand.Set( pBucket );
}
//
//=============================================================
// release the lock on table
//
//
// Convert this single bucket to a window.
//
_NoLokBucketToWindows( xBktToExpand, widToPin, isWatched, fOptimizeBucketization );
} while ( 0 != xSegStack.Count() );
} //_NoLokBucketToWindows
//+---------------------------------------------------------------------------
//
// Method: CLargeTable::GetRowsAt
//
// Synopsis: Fetch rows relative to a bookmark
//
// Arguments: [hRegion] - watch region handle
// [widStart] -
// [chapt] - Chapter from which to fetch rows (if chaptered)
// [iRowOffset] -
// [rOutColumns] -
// [rGetParams] -
// [rwidLastRowTransferred] -
//
// Returns:
//
// History: 3-31-95 srikants Created
// 6-28-95 BartoszM Added watch region support
//
// Notes:
//
//----------------------------------------------------------------------------
SCODE
CLargeTable::GetRowsAt(
HWATCHREGION hRegion,
WORKID widStart,
CI_TBL_CHAPT chapt,
DBROWOFFSET iRowOffset,
CTableColumnSet const & rOutColumns,
CGetRowsParams & rGetParams,
WORKID & rwidLastRowTransferred
)
{
//
// DO NOT OBTAIN LOCK HERE.
// If the row of interest is in an un-sorted bucket, it must be
// expanded into a window before we can determine the exact wid.
// For bucket->window expansion, we have to release the lock because
// the conversion (not now but later) will be done by a worker thread
// and we will deadlock.
//
tbDebugOut(( DEB_REGTRANS,
" =============== Entering GetRowsAt ========= \n\n " ));
if ( iRowOffset > 0 )
rwidLastRowTransferred = WORKID_TBLLAST;
else
rwidLastRowTransferred = WORKID_TBLFIRST;
BOOL fAsync = FALSE;
if ( WORKID_TBLBEFOREFIRST == widStart )
{
if (iRowOffset <= 0)
{
widStart = WORKID_TBLLAST;
}
else
{
widStart = WORKID_TBLFIRST;
iRowOffset--;
}
}
else if ( WORKID_TBLAFTERLAST == widStart )
{
widStart = WORKID_TBLLAST;
iRowOffset++;
}
tbDebugOut(( DEB_REGTRANS, " widstart 0x%x, offset %d\n", widStart, iRowOffset ));
ULONG cChaptRows = 0;
if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
{
CLock lock(_mutex);
cChaptRows = GetCategorizer()->GetRowCount( chapt );
if ( WORKID_TBLFIRST == widStart || WORKID_TBLLAST == widStart )
{
BOOL isLastBmk = WORKID_TBLLAST == widStart;
widStart = GetCategorizer()->GetFirstWorkid( chapt );
if ( isLastBmk )
{
iRowOffset += ( cChaptRows - 1 );
}
if ( ( isLastBmk ) &&
( -1 == iRowOffset ) &&
( 1 == cChaptRows ) )
{
iRowOffset = 0;
}
}
}
CTableRowLocator rowLocator( *this, widStart, (LONG) iRowOffset, chapt );
CTableRowGetter tableRowGetter( *this, rOutColumns, rGetParams, chapt, hRegion );
SCODE status = S_OK;
CWatchRegion* pWatchRegion = 0;
BOOL fBeyondTable = FALSE; // flag indicating if we had to go
// beyond the end of the table.
CDynStack<CTableBucket> xBktToConvert(0); // buckets to expand at our leisure
WORKID widToPin = widInvalid; // The workid we need to pin.
for (;;)
{
//
// Loop as long as there are buckets to be sychronously
// expanded.
//
XPtr<CTableBucket> xBktToExplode(0); // bucket to explode synchronously
// ============================================================
{
CLock lock(_mutex);
_LokCheckQueryStatus();
fAsync = _LokIsWatched() && (0 != hRegion);
if (!_watchList.IsEmpty())
{
_watchList.VerifyRegion (hRegion);
// valid region or null
pWatchRegion = _watchList.GetRegion(hRegion);
}
else if (hRegion != 0)
{
THROW (CException(E_INVALIDARG));
}
#if CIDBG==1
if ( 0 != pWatchRegion && pWatchRegion->IsInit() )
{
Win4Assert( pWatchRegion->Segment()->IsWindow() );
CTableWindow * pWindow = (CTableWindow *) pWatchRegion->Segment();
Win4Assert( pWindow->HasWatch( hRegion ) );
}
#endif // CIDBG==1
CRegionTransformer regionTransformer( pWatchRegion,
(LONG) iRowOffset,
rGetParams.RowsToTransfer(),
rGetParams.GetFwdFetch() );
CFwdTableSegIter iter( _segList );
//
// Locate the bookmark.
//
status = rowLocator.LokLocate( hRegion,
fAsync,
iter,
regionTransformer);
if ( S_OK != status )
{
return status;
}
// also: calculate the fetch coordinates
if ( !regionTransformer.Validate() )
THROW (CException(DB_E_NONCONTIGUOUSRANGE));
// The iterator is positioned at the fetch bookmark
// Move it to the actual fetch offset
rowLocator.LokRelocate ( fAsync,
iter,
regionTransformer,
xBktToExplode,
xBktToConvert );
if ( xBktToExplode.IsNull() )
{
//
// The first row to be fetched is in a window.
//
if ( 0 != rowLocator.GetBeyondTableCount() )
{
tbDebugOut(( DEB_REGTRANS,
" GetBeyondTableCount: %d\n",
rowLocator.GetBeyondTableCount() ));
//
// The requested offset is beyond the table. Must update
// the row retrieval count based on the residual row
// count.
//
regionTransformer.DecrementFetchCount( rowLocator,
iter,
_segList );
// to support watch regions, fetches beyond the
// end of rowset are expected
fBeyondTable = TRUE;
// don't read any rows that do fall in the table
// if notifications aren't enabled.
if ( 0 == _bitNotifyEnabled )
break;
}
Win4Assert( regionTransformer.GetFetchCount() >= 0 );
if ( regionTransformer.GetFetchCount() > 0 )
{
Win4Assert( iter.GetSegment()->IsWindow() );
if ( 0 != regionTransformer.Region() )
{
CDoubleTableSegIter fakeIter( iter );
//
// Simulate a fetch and collect all the buckets to be
// asynchronously expanded.
//
rowLocator.LokSimulateFetch( fakeIter,
regionTransformer,
xBktToConvert );
regionTransformer.Transform (_segList,
_watchList );
}
//
// We have located exactly where the starting workid is.
// Start fetching rows from here.
//
widStart = rowLocator.GetWorkIdFound();
tableRowGetter.SetRowsToTransfer( (ULONG) regionTransformer.GetFetchCount() );
//
// Real Work done here!
//
CTableWindow * pWindow = iter.GetWindow();
status = tableRowGetter.LokGetRowsAtSegment ( pWindow,
widStart,
fAsync,
xBktToExplode );
rwidLastRowTransferred = tableRowGetter.GetLastWorkId();
if ( !xBktToExplode.IsNull() )
{
Win4Assert( !fAsync );
//
// All the requested rows did not get filled in.
// We have hit a bucket which must be exploded.
// Snapshot the current position and offset (either
// +1 if forward fetch or -1 if backwards fetch) so
// that we can continue from the snapshotted position
// after the bucket has been exploded.
//
long iOffset;
if ( rGetParams.GetFwdFetch() )
iOffset = 1;
else
iOffset = -1;
rowLocator.SetLocateInfo( rwidLastRowTransferred, iOffset );
widToPin = rwidLastRowTransferred;
iRowOffset = iOffset;
}
} // regionTransformer.GetFetchCount() > 0
}
#if CIDBG==1
_watchList.CheckRegionConsistency( pWatchRegion );
#endif // CIDBG==1
}
// Lock released
// ============================================================
if ( 0 != xBktToExplode.GetPointer() )
{
// We need to synchronously convert bucket into windows.
// and then restart fetching from the top
if ( widInvalid == widToPin )
widToPin = widStart;
_NoLokBucketToWindows( xBktToExplode, widToPin, FALSE, FALSE );
}
else
{
break;
}
} // end of bucket expansion loop
if ( xBktToConvert.Count() > 0 )
{
// Schedule buckets for asynchronous expansion
_NoLokBucketToWindows ( xBktToConvert, widInvalid, TRUE, TRUE );
}
if (SUCCEEDED(status) && fBeyondTable)
{
if ( _bitNotifyEnabled )
status = DB_S_ENDOFROWSET;
else
status = DB_E_BADSTARTPOSITION;
}
else if ( ( IsCategorized() ) &&
( DB_S_ENDOFROWSET == status ) &&
( 0 == rGetParams.RowsTransferred() ) &&
( 0 != cChaptRows ) )
{
status = DB_E_BADSTARTPOSITION;
}
//
// If the query timed out and we're fetching at the end of the
// rowset, give an appropriate status.
//
if ( ( DB_S_ENDOFROWSET == status ) &&
( 0 != ( Status() & STAT_TIME_LIMIT_EXCEEDED ) ) )
{
status = DB_S_STOPLIMITREACHED;
}
return status;
} //GetRowsAt
//+---------------------------------------------------------------------------
//
// Function: GetRowsAtRatio
//
// Synopsis: An APPROXIMATE retrieval of rows. NOTE that this is not
// EXACT - use GetRowsAt to retrieve rows at exact position.
//
// Arguments: [hRegion] - watch region handle
// [ulNum] -
// [ulDenom] -
// [chapt] - Chapter of rows returned
// [rOutColumns] -
// [rGetParams] -
// [rwidLastRowTransferred] -
//
// Returns:
//
// History: 4-04-95 srikants Created
// 6-28-95 BartoszM Added watch region support
//
// Notes:
//
//----------------------------------------------------------------------------
SCODE
CLargeTable::GetRowsAtRatio(
HWATCHREGION hRegion,
ULONG ulNum,
ULONG ulDenom,
CI_TBL_CHAPT chapt,
CTableColumnSet const & rOutColumns,
CGetRowsParams & rGetParams,
WORKID & rwidLastRowTransferred
)
{
if ( 0 == ulDenom || ulNum > ulDenom )
{
QUIETTHROW( CException(DB_E_BADRATIO) );
}
BOOL fFarFromWindow = TRUE;
BOOL fAsync = FALSE;
SCODE scRet = S_OK;
ULONG cRowsFromFront = 0;
WORKID widAnchor = widInvalid; // initialize to an invalid value.
ULONG cPercentDiff = 0;
LONG offFetchStart = 0;
XPtr<CTableBucket> xBktToExplode(0);
//
// ==================================================================
// Obtain the lock
{
CLock lock(_mutex);
_LokCheckQueryStatus();
if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
{
ULONG cRows = GetCategorizer()->GetRowCount( chapt );
Win4Assert( 0 != cRows && "Chapter is empty" );
LONGLONG llcRowsFromFront = ( (LONGLONG) cRows) * ulNum ;
llcRowsFromFront /= ulDenom;
Win4Assert( llcRowsFromFront <= cRows && llcRowsFromFront >= 0 );
cRowsFromFront = lltoul( llcRowsFromFront ) ;
}
else
{
//
// Determine the approximate offset from the beginning of the table
//
const ULONG cTotalRows = (ULONG) RowCount();
if ( 0 == cTotalRows )
{
rwidLastRowTransferred = WORKID_TBLAFTERLAST;
return DB_S_ENDOFROWSET;
}
LONGLONG llcRowsFromFront = ( (LONGLONG) cTotalRows) * ulNum ;
llcRowsFromFront /= ulDenom;
Win4Assert( llcRowsFromFront <= cTotalRows && llcRowsFromFront >= 0 );
cRowsFromFront = lltoul( llcRowsFromFront ) ;
if ( cRowsFromFront == cTotalRows )
{
if ( rGetParams.GetFwdFetch() )
{
//
// The user is asking to retrieve past the end of table.
//
rwidLastRowTransferred = WORKID_TBLAFTERLAST;
return DB_S_ENDOFROWSET;
}
else
{
//
// Fetch rows starting with last row in rowset
//
rwidLastRowTransferred = WORKID_TBLAFTERLAST;
SCODE scRet = GetRowsAt( hRegion,
WORKID_TBLAFTERLAST,
chapt,
-1,
rOutColumns,
rGetParams,
rwidLastRowTransferred );
return scRet;
}
}
//
// Locate the closest window at this offset.
//
CLinearRange winRange; // will be initialized by _LokGetClosestWindow
CTableWindow * pWindow = _LokGetClosestWindow( cRowsFromFront, winRange );
if ( 0 == pWindow )
{
fFarFromWindow = TRUE;
}
else
{
//
// Offset of the first row from the beginning of the table.
//
offFetchStart = winRange.GetLow();
if ( winRange.InRange( cRowsFromFront ) &&
rGetParams.RowsToTransfer() <= winRange.GetRange() )
{
//
// This window has enough rows.
//
long cRowsOff; // offset within the window
if ( rGetParams.GetFwdFetch() )
{
if ( cRowsFromFront + rGetParams.RowsToTransfer() <=
winRange.GetHigh() )
{
//
// We hit the exact row that the client requested.
//
cRowsOff = cRowsFromFront - winRange.GetLow();
}
else
{
//
// Optimization : We cannot fill the output set with the rows
// from this window if we position exactly. So just use a
// little from front.
//
cRowsOff = winRange.GetRange() - rGetParams.RowsToTransfer();
}
}
else
{
if ( cRowsFromFront >= rGetParams.RowsToTransfer()
&& cRowsFromFront - rGetParams.RowsToTransfer() >= winRange.GetLow() )
{
//
// We hit the exact row that the client requested
//
cRowsOff = cRowsFromFront - winRange.GetLow();
}
else
{
//
// Optimization : We cannot fill the output set with the rows
// from this window if we position exactly. So just use a
// little from back.
//
cRowsOff = rGetParams.RowsToTransfer() - 1;
}
}
//
// There was no wrap around.
//
Win4Assert( cRowsOff >= 0 && cRowsOff < (long)pWindow->RowCount());
offFetchStart += cRowsOff;
widAnchor = pWindow->GetBookMarkAt( cRowsOff );
}
else
{
widAnchor = WORKID_TBLFIRST;
}
const cMaxApproxPerCent = 10; // Let us say 10% maximum approximation
//
// Determine how accurate is the approximate position.
//
cPercentDiff = AbsDiff( offFetchStart, cRowsFromFront ) * 100;
cPercentDiff /= cTotalRows;
if ( cPercentDiff <= cMaxApproxPerCent )
{
Win4Assert( widInvalid != widAnchor );
fFarFromWindow = FALSE;
}
else
{
tbDebugOut(( DEB_WINSPLIT,
" Approximation is too off 0x%X - Requested - Arrived 0x%X\n",
cRowsFromFront, offFetchStart ));
}
}
fAsync = _LokIsWatched() && (0 != hRegion);
if ( 0 != hRegion )
{
// Nuke the region first
_watchList.ShrinkRegionToZero (hRegion);
}
//
// If we are either too far from a window or if the asynchronous
// mode is on, we have to use GetRowsAt()
//
if ( !fFarFromWindow && !fAsync )
{
tbDebugOut(( DEB_WINSPLIT,
"GetRowsAtRatio - Approximate. Requested 0x%X Actual 0x%X Percent 0x%X\n",
cRowsFromFront, offFetchStart, cPercentDiff ));
CTableRowGetter rowGetter( *this, rOutColumns, rGetParams, chapt, hRegion );
//
// Real work done here!
//
Win4Assert( widInvalid != widAnchor );
Win4Assert( 0 != pWindow->RowCount() );
#if CIDBG==1
TBL_OFF oRowdummy;
ULONG iRowdummy;
Win4Assert( pWindow->FindBookMark( widAnchor, oRowdummy, iRowdummy ) );
#endif // CIDBG==1
scRet = rowGetter.LokGetRowsAtSegment( pWindow,
widAnchor,
FALSE, // synchronous
xBktToExplode );
if ( !FAILED(scRet) )
{
rwidLastRowTransferred = rowGetter.GetLastWorkId();
Win4Assert( rwidLastRowTransferred != widInvalid &&
!IsSpecialWid( rwidLastRowTransferred ) );
}
if ( xBktToExplode.IsNull() )
{
return scRet;
}
//
// The bucket to explode is not null. We will synchronously
// explode it and continue fetching from there on.
//
Win4Assert( !FAILED(scRet) );
}
}
}
// ==================================================================
// Release table lock
//
// We are either :
// 1. Far from window or
// 2. Is being watched or
// 3. We have a bucket to explode synchronously.
//
if ( xBktToExplode.IsNull() )
{
//
// We are either far from a window or we have a watch region.
//
widAnchor = WORKID_TBLFIRST;
offFetchStart = cRowsFromFront;
}
else
{
widAnchor = rwidLastRowTransferred;
if ( rGetParams.GetFwdFetch() )
offFetchStart = 1;
else
offFetchStart = -1;
_NoLokBucketToWindows( xBktToExplode, widAnchor, FALSE, TRUE ); // synchronous
}
tbDebugOut(( DEB_WINSPLIT, "GetRowsAtRatio - going to GetRowsAt\n" ));
Win4Assert( widInvalid != widAnchor );
// The region is pre-shrunk to zero, so it's okay to start in a bucket.
scRet = GetRowsAt( hRegion,
widAnchor,
chapt,
offFetchStart,
rOutColumns,
rGetParams,
rwidLastRowTransferred );
if (DB_E_BADSTARTPOSITION == scRet)
scRet = DB_S_ENDOFROWSET;
return scRet;
} //GetRowsAtRatio
//+---------------------------------------------------------------------------
//
// Class: CClosestWindow
//
// Purpose: Determines the closest window for a given position in the
// table.
//
// History: 4-03-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
class CClosestWindow
{
public:
CClosestWindow( ULONG targetOffset, CTableSegList & list )
: _targetOffset(targetOffset),
_list(list),
_iter(_list),
_currSegOffset(0),
_pBest(0), _bestRange(ULONG_MAX,ULONG_MAX),
_fDone(FALSE)
{
}
void ProcessCurrent();
BOOL IsDone() { return _fDone || _list.AtEnd(_iter); }
CTableWindow * GetClosest( CLinearRange & winRange )
{
if ( 0 != _pBest )
{
winRange.Set( _bestRange.GetLow(), _bestRange.GetHigh() );
}
return _pBest;
}
void Next()
{
Win4Assert( !_list.AtEnd(_iter) );
_list.Advance(_iter);
}
private:
ULONG _targetOffset; // Target offset to locate
CTableSegList & _list; // The list of segments
CFwdTableSegIter _iter; // Iterator over the list of segments
ULONG _currSegOffset; // Beginning offset of the current
// segment
CTableWindow * _pBest; // Best window so far
CLinearRange _bestRange; // Range of the best window
BOOL _fDone; // Flag set to TRUE if we have already
// found the best possible window.
};
//+---------------------------------------------------------------------------
//
// Function: ProcessCurrent
//
// Synopsis: Processes the current segment in the iterator and updates
// the "best" window if applicable.
//
// History: 4-03-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CClosestWindow::ProcessCurrent( )
{
CTableSegment * pSegment = _iter.GetSegment();
Win4Assert( 0 != pSegment );
Win4Assert( !_fDone );
ULONG cRowsInSeg = (ULONG)pSegment->RowCount();
ULONG currEndSeg = _currSegOffset;
if ( 0 != cRowsInSeg )
{
currEndSeg += (cRowsInSeg-1);
}
if ( pSegment->IsWindow() && 0 != pSegment->RowCount() )
{
if ( _currSegOffset <= _targetOffset )
{
//
// We are still on the left hand side. So, this one MUST be
// closer than what we had before.
//
_pBest = (CTableWindow *) pSegment;
_bestRange.Set( _currSegOffset, currEndSeg );
_fDone = _bestRange.InRange( _targetOffset );
}
else
{
//
// We are on the right hand side of the target.
//
CTableWindow * pRight = (CTableWindow *) pSegment;
if ( 0 != _pBest )
{
if ( AbsDiff( _currSegOffset, _targetOffset ) <
AbsDiff( _bestRange.GetLow(), _targetOffset ) )
{
//
// We found a closer window on the right hand
// side.
//
_pBest = pRight;
_bestRange.Set( _currSegOffset, currEndSeg );
}
}
else
{
_pBest = pRight;
_bestRange.Set( _currSegOffset, currEndSeg );
}
_fDone = TRUE;
}
}
_currSegOffset += cRowsInSeg;
}
//+---------------------------------------------------------------------------
//
// Function: _LokGetClosestWindow
//
// Synopsis: Given a position from the beginning of the table, this
// method determines the closest window from that position.
//
// Arguments: [cRowsFromFront] - Number of rows from the beginning of
// the table.
// [winRange] - (output) Range of the window found in
// the table.
//
// Returns: Pointer to the window, if one is found that is closest to
// the position requested.
//
// History: 4-03-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
CTableWindow * CLargeTable::_LokGetClosestWindow(
ULONG cRowsFromFront,
CLinearRange & winRange )
{
winRange.Set(0,0);
for ( CClosestWindow closest( cRowsFromFront, _segList );
!closest.IsDone();
closest.Next() )
{
closest.ProcessCurrent();
}
return closest.GetClosest( winRange );
}
//+---------------------------------------------------------------------------
//
// Function: _CreateNewWindow
//
// Synopsis: Creates a new window with the given segment id.
//
// Arguments: [segId] - Segment id of the window to create.
//
// Returns: Pointer to the newly created window.
//
// History: 4-19-95 srikants Created
//
// Notes: This is a helper function for "CTableRowPutter" class.
//
//----------------------------------------------------------------------------
CTableWindow * CLargeTable::_CreateNewWindow(
ULONG segId,
CTableRowKey &lowKey,
CTableRowKey &highKey)
{
XPtr<CTableWindow> xWindow( new CTableWindow( _pSortSet,
_keyCompare.GetReference(),
&_MasterColumnSet,
segId,
GetCategorizer(),
_sharedBuf,
*_pQExecute ) );
xWindow->GetLowestKey() = lowKey;
xWindow->GetHighestKey() = highKey;
return xWindow.Acquire();
} //_CreateNewWindow
//+---------------------------------------------------------------------------
//
// Function: SetQueryExecute
//
// Synopsis: Initializes the query executer object to be used during
// bucket->window conversion. The query object will be refcounted
// to co-ordinate destruction order.
//
// Arguments: [pQExecute] - Pointer to the query object.
//
// History: 4-25-95 srikants Created
//
// Notes: Must be called ONCE and only ONCE. Before ~CLargeTable is
// called, the ReleaseQueryExecute MUST be called.
//
//----------------------------------------------------------------------------
void CLargeTable::SetQueryExecute( CQAsyncExecute * pQExecute ) // virtual
{
CLock lock(_mutex);
Win4Assert( 0 == _pQExecute );
_pQExecute = pQExecute;
_pQExecute->AddRef();
}
//+---------------------------------------------------------------------------
//
// Function: ReleaseQueryExecute
//
// Synopsis: Releases the query object. After this, we cannot do any
// bucket->window conversions.
//
// History: 4-25-95 srikants Created
//
// Notes: MUST be called once before the destructor is called.
//
//----------------------------------------------------------------------------
void CLargeTable::ReleaseQueryExecute()
{
CLock lock(_mutex);
Win4Assert( 0 != _pQExecute );
_pQExecute->Release();
_pQExecute = 0;
//
// Abort all the bucket->window expansions that are in progress.
//
for ( CAsyncBucketExploder * pEntry = _explodeBktsList.RemoveLast();
0 != pEntry;
pEntry = _explodeBktsList.RemoveLast() )
{
pEntry->Close();
pEntry->Abort();
pEntry->Release();
}
}
//+---------------------------------------------------------------------------
//
// Function: _LokAddToExplodeList
//
// Synopsis:
//
// Arguments: [pBktExploder] -
//
// Returns:
//
// History: 5-30-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CLargeTable::_LokAddToExplodeList( CAsyncBucketExploder * pBktExploder )
{
Win4Assert( pBktExploder->IsSingle() );
pBktExploder->AddRef();
_explodeBktsList.Push( pBktExploder );
//
// Pin the workid to be in a window and prevent from being converted
// into a bucket.
//
_segListMgr.SetInUseByBuckets( pBktExploder->GetWorkIdToPin() );
} //_LokAddToExplodeList
//+---------------------------------------------------------------------------
//
// Function: _RemoveFromExplodeList
//
// Synopsis:
//
// Arguments: [pBktExploder] -
//
// Returns:
//
// History: 5-30-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CLargeTable::_RemoveFromExplodeList( CAsyncBucketExploder * pBktExploder )
{
{
CLock lock(_mutex);
Win4Assert( 0 != pBktExploder );
_explodeBktsList.RemoveFromList( pBktExploder );
pBktExploder->Close();
_segListMgr.ClearInUseByBuckets( pBktExploder->GetWorkIdToPin() );
}
pBktExploder->Release();
}
//+---------------------------------------------------------------------------
//
// Function: QueryAbort
//
// Synopsis: Processes an abort and wakes up any waiters on the events
// in the largetable.
//
// History: 5-31-95 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CLargeTable::QueryAbort()
{
CLock lock(_mutex);
Win4Assert( 0 == _pQExecute );
Win4Assert( _explodeBktsList.IsEmpty() );
_fAbort = TRUE;
} //QueryAbort
//+---------------------------------------------------------------------------
//
// Method: GetCurrentPosition, public
//
// Synopsis: Gets the current GetNextRows position for the table or
// chapter.
//
// Arguments: [chapt] - gets the current position for this chapter
//
// History: 6-30-95 dlee Created
//
//----------------------------------------------------------------------------
WORKID CLargeTable::GetCurrentPosition(
CI_TBL_CHAPT chapt)
{
if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
return GetCategorizer()->GetCurrentPositionThisLevel( chapt );
else
return _widCurrent;
} //GetCurrentPosition
//+---------------------------------------------------------------------------
//
// Method: SetCurrentPosition, public
//
// Synopsis: Sets the current GetNextRows position for the table or
// chapter.
//
// Arguments: [chapt] - sets the current position for this chapter
//
// History: 6-30-95 dlee Created
//
//----------------------------------------------------------------------------
WORKID CLargeTable::SetCurrentPosition(
CI_TBL_CHAPT chapt,
WORKID wid)
{
if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
return GetCategorizer()->SetCurrentPositionThisLevel( chapt, wid );
else
return ( _widCurrent = wid );
} //SetCurrentPosition
//+---------------------------------------------------------------------------
//
// Method: LokGetOneColumn, public
//
// Synopsis: Gets data for one column for the given workid.
//
// Arguments: [wid] - for which data is retrieved
// [rOutColumn] - column descritption for where data is written
// [pbOut] - where data is written
// [rVarAllocator] - allocator to use for variable-len data
//
// History: 8-22-95 dlee Created
//
//----------------------------------------------------------------------------
void CLargeTable::LokGetOneColumn(
WORKID wid,
CTableColumn const & rOutColumn,
BYTE * pbOut,
PVarAllocator & rVarAllocator )
{
CTableWindow *pWindow = (CTableWindow *) _LokFindTableSegment( wid );
pWindow->LokGetOneColumn( wid, rOutColumn, pbOut, rVarAllocator );
} //LokGetOneColumn
CI_TBL_BMK CLargeTable::_FindNearestDynamicBmk( CTableWindow * pWindow,
CI_TBL_CHAPT chapter,
CI_TBL_BMK bookmark )
{
// If the row corresponding to the bookmark exists in the
// dynamic state, return the original bookmark.
// If the row has been deleted from the dynamic state
// return the bookmark of the next available dynamic row.
// If you hit the end of the table, return the bookmark
// of the last row in the dynamic state of the table
CI_TBL_BMK bmkFound = bookmark;
if ( pWindow->FindNearestDynamicBmk( chapter, bmkFound ) )
{
return bmkFound;
}
//
// Find the closest bookmark to the right of this window.
//
CDoubleTableSegIter fwdIter( pWindow );
for ( _segList.Advance(fwdIter); !_segList.AtEnd(fwdIter);
_segList.Advance(fwdIter) )
{
if ( fwdIter.GetSegment()->IsWindow() )
{
CTableWindow * pWindow = fwdIter.GetWindow();
if ( pWindow->FindFirstNonDeleteDynamicBmk(bmkFound) )
return bmkFound;
}
}
//
// Couldn't find anything to the right of this window. Find to the
// left of the window.
//
CDoubleTableSegIter backIter( pWindow );
for ( _segList.BackUp(backIter); !_segList.AtEnd(backIter);
_segList.BackUp(backIter) )
{
if ( backIter.GetSegment()->IsWindow() )
{
CTableWindow * pWindow = backIter.GetWindow();
if ( pWindow->FindFirstNonDeleteDynamicBmk(bmkFound) )
return bmkFound;
}
}
//
// The whole table has been deleted. Just use the WORKID_TBLFIRST
//
return WORKID_TBLFIRST;
} //_FindNearestDynamicBmk
//+---------------------------------------------------------------------------
//
// Member: CTableSink::SetStatus, public
//
// Synopsis: Sets the query status.
//
// Arguments: [s] -- The new status
//
// History: 18-Oct-91 KyleP Created.
//
//----------------------------------------------------------------------------
void CTableSink::SetStatus(ULONG s, NTSTATUS sc)
{
_status = s;
if (sc != STATUS_SUCCESS)
{
Win4Assert( QUERY_FILL_STATUS(s) == STAT_ERROR &&
! NT_SUCCESS(sc) );
_scStatus = sc;
tbDebugOut(( DEB_WARN,
"tablesink at 0x%x entering 0x%x ERROR state\n",
this, sc ));
}
else
{
Win4Assert( QUERY_FILL_STATUS(s) != STAT_ERROR );
}
}