4028 lines
120 KiB
C++
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 );
|
|
}
|
|
}
|
|
|