mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3084 lines
93 KiB
3084 lines
93 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1994 - 2000.
|
|
//
|
|
// File: tblwindo.cxx
|
|
//
|
|
// Contents:
|
|
//
|
|
// Classes: CTableWindow - a window over a segment of a table
|
|
// XCompressFreeVariant - container for variant which must be freed
|
|
//
|
|
// Functions:
|
|
//
|
|
// Notes:
|
|
//
|
|
// History: 25 Jan 1994 AlanW Created
|
|
// 20 Jun 1995 BartoszM Added watch regions
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#include "pch.cxx"
|
|
#pragma hdrstop
|
|
|
|
#include <query.hxx> // For CGetRowsParams
|
|
#include <tbrowkey.hxx>
|
|
#include <singlcur.hxx>
|
|
|
|
#include "tblwindo.hxx"
|
|
#include "colcompr.hxx"
|
|
#include "tabledbg.hxx"
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::CTableWindow, public
|
|
//
|
|
// Synopsis: Constructor for a window. Allocates and fills
|
|
// in the column description. Computes size of
|
|
// per-row data and assigns column offsets.
|
|
// Allocates initial table row data and variable
|
|
// data. Allocates initial row index.
|
|
//
|
|
// Arguments: [pMasterColumns] -- a pointer to the master column set
|
|
//
|
|
// Notes: PERFFIX - vikasman: To save space, we can set up all columns
|
|
// which are not part of the sort set and which are not special
|
|
// columns as deferred columns.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CTableWindow::CTableWindow(
|
|
CSortSet const * pSortSet,
|
|
CTableKeyCompare & comparator,
|
|
CColumnMasterSet * pMasterColumns,
|
|
ULONG segId,
|
|
CCategorize *pCategorize,
|
|
CSharedBuffer & sharedBuf,
|
|
CQAsyncExecute & QExecute
|
|
) : CTableSegment( CTableSegment::eWindow, segId,
|
|
*pSortSet, comparator ),
|
|
_pSortSet( pSortSet ),
|
|
_Columns( pMasterColumns->Size() ),
|
|
_sharedBuf( sharedBuf ),
|
|
_BookMarkMap(_visibleRowIndex),
|
|
_cPendingDeletes(0),
|
|
_cRowsHardDeleted(0),
|
|
_cRowsAllocated(0),
|
|
_fSplitInProgress(FALSE),
|
|
_cbHeapUsed ( 0 ),
|
|
_pPathStore(0),
|
|
_dynRowIndex(),
|
|
_QueryExecute(QExecute),
|
|
_fCanPartialDefer(QExecute.CanPartialDefer())
|
|
{
|
|
tbDebugOut(( DEB_ITRACE, "_CanPartialDefer = %d\n", _fCanPartialDefer ));
|
|
|
|
SetCategorizer(pCategorize);
|
|
|
|
int cCol = pMasterColumns->Size();
|
|
|
|
CTableRowAlloc RowMap(0);
|
|
|
|
unsigned maxAlignment = 0;
|
|
_iOffsetWid = ULONG_MAX;
|
|
_iOffsetRowStatus = ULONG_MAX;
|
|
_iOffsetChapter = ULONG_MAX;
|
|
_iOffsetRank = ULONG_MAX;
|
|
_iOffsetHitCount = ULONG_MAX;
|
|
|
|
for (int iCol=0; iCol < cCol; iCol++) {
|
|
CColumnMasterDesc& MasterCol = pMasterColumns->Get(iCol);
|
|
|
|
if (MasterCol.IsCompressedCol() &&
|
|
MasterCol.GetCompressMasterId() != 0)
|
|
{
|
|
//
|
|
// Global shared compression; make sure the referenced
|
|
// column is in the table column set.
|
|
//
|
|
|
|
BOOL fFound = FALSE;
|
|
CTableColumn* pColumn =
|
|
_Columns.Find(MasterCol.GetCompressMasterId(), fFound);
|
|
|
|
if (fFound != TRUE) {
|
|
CColumnMasterDesc* pMasterComprCol =
|
|
pMasterColumns->Find(MasterCol.GetCompressMasterId());
|
|
|
|
Win4Assert(pMasterComprCol != 0);
|
|
_AddColumnDesc(*pMasterComprCol, RowMap, maxAlignment);
|
|
}
|
|
}
|
|
else if ( pidName == MasterCol.PropId )
|
|
{
|
|
//
|
|
// We must always add the pidPath before pidName if it is
|
|
// present in the MasterColumnSet. This is because we
|
|
// do shared compression for path and name.
|
|
//
|
|
BOOL fPathPresent = FALSE;
|
|
CTableColumn * pWindowPathCol = _Columns.Find( pidPath ,
|
|
fPathPresent );
|
|
if ( !fPathPresent )
|
|
{
|
|
//
|
|
// There is no column for pidPath in the window before
|
|
// this column. Check if there is pidPath in the master
|
|
// column set and if so, add pidPath to the window column
|
|
// set before pidName.
|
|
//
|
|
CColumnMasterDesc * pMasterPathCol =
|
|
pMasterColumns->Find( pidPath );
|
|
if ( 0 != pMasterPathCol )
|
|
{
|
|
_AddColumnDesc( *pMasterPathCol, RowMap, maxAlignment );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// See if the column is already there (added for a shared compr)
|
|
//
|
|
BOOL fFound = FALSE;
|
|
CTableColumn* pColumn =
|
|
_Columns.Find(MasterCol.PropId, fFound);
|
|
|
|
if (fFound)
|
|
continue;
|
|
|
|
|
|
_AddColumnDesc(MasterCol, RowMap, maxAlignment);
|
|
}
|
|
|
|
_FinishInit( RowMap, maxAlignment );
|
|
|
|
END_CONSTRUCTION(CTableWindow);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CTableWindow ~ctor
|
|
//
|
|
// Synopsis: Constructor used for creating a new table window with the
|
|
// same structure as an existing table window.
|
|
//
|
|
// Arguments: [src] - Source window which should be used as a basis for
|
|
// the ROW FORMAT only; not the data.
|
|
//
|
|
// History: 2-07-95 srikants Created
|
|
//
|
|
// Notes: This is used during window splitting to create new windows
|
|
// from an existing window.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CTableWindow::CTableWindow(
|
|
CTableWindow & src,
|
|
ULONG segId
|
|
) : CTableSegment( src, segId ),
|
|
_Columns( src._Columns.Count() ),
|
|
_sharedBuf( src._GetSharedBuf() ),
|
|
_BookMarkMap(_visibleRowIndex),
|
|
_pSortSet( src._pSortSet ),
|
|
_cPendingDeletes(0),
|
|
_cRowsHardDeleted(0),
|
|
_cRowsAllocated(0),
|
|
_fSplitInProgress(FALSE),
|
|
_cbHeapUsed ( 0 ),
|
|
_pPathStore(0),
|
|
_dynRowIndex(),
|
|
_QueryExecute(src._QueryExecute),
|
|
_fCanPartialDefer(src._fCanPartialDefer)
|
|
{
|
|
SetCategorizer(src.GetCategorizer());
|
|
|
|
int cCol = src._Columns.Count();
|
|
|
|
CTableRowAlloc RowMap(0);
|
|
|
|
unsigned maxAlignment = 0;
|
|
_iOffsetWid = ULONG_MAX;
|
|
_iOffsetRowStatus = ULONG_MAX;
|
|
_iOffsetChapter = ULONG_MAX;
|
|
_iOffsetRank = ULONG_MAX;
|
|
_iOffsetHitCount = ULONG_MAX;
|
|
|
|
//
|
|
// PERFFIX: if we don't do window local compression, then we
|
|
// can just copy the column format bit-by-bit and not worry
|
|
// about looking at each column individually. For now, I will leave
|
|
// the loop in place.
|
|
//
|
|
for (int iCol=0; iCol < cCol; iCol++)
|
|
{
|
|
|
|
CTableColumn& tableCol = *src._Columns.Get(iCol);
|
|
|
|
if ( tableCol.IsCompressedCol() &&
|
|
0 != tableCol.GetCompressMasterId() )
|
|
{
|
|
|
|
//
|
|
// Global shared compression; make sure the referenced
|
|
// column is in the table column set.
|
|
//
|
|
|
|
BOOL fAlreadyPresent = FALSE;
|
|
CTableColumn* pColumn =
|
|
_Columns.Find(tableCol.GetCompressMasterId(), fAlreadyPresent);
|
|
|
|
Win4Assert( fAlreadyPresent );
|
|
|
|
if (!fAlreadyPresent)
|
|
{
|
|
CTableColumn* pTableComprCol =
|
|
src._Columns.Find( tableCol.GetCompressMasterId(),
|
|
fAlreadyPresent );
|
|
|
|
Win4Assert(0 != pTableComprCol);
|
|
_AddColumnDesc(*pTableComprCol, RowMap, maxAlignment);
|
|
}
|
|
}
|
|
|
|
//
|
|
// See if the column is already there (added for a shared compr)
|
|
//
|
|
BOOL fFound = FALSE;
|
|
CTableColumn* pColumn =
|
|
_Columns.Find(tableCol.GetPropId(), fFound);
|
|
|
|
if (!fFound)
|
|
{
|
|
_AddColumnDesc(tableCol, RowMap, maxAlignment);
|
|
}
|
|
}
|
|
|
|
_FinishInit( RowMap, maxAlignment );
|
|
// tbDebugOut(( DEB_WINSPLIT, "NewWindow-C with id 0x%X\n", segId ));
|
|
|
|
END_CONSTRUCTION(CTableWindow);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: _FinishInit
|
|
//
|
|
// Synopsis: Completes the initialization of the constructor.
|
|
//
|
|
// Arguments: [RowMap] - RowMap containing the allocation details for
|
|
// the column
|
|
// [maxAlignment] - Maximum alignment requirement
|
|
//
|
|
// History: 2-07-95 srikants Split from the ~ctor to move the common
|
|
// code to a function.
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_FinishInit( CTableRowAlloc & RowMap,
|
|
unsigned & maxAlignment )
|
|
{
|
|
|
|
Win4Assert(_iOffsetWid != ULONG_MAX); // workid must be there
|
|
Win4Assert(_iOffsetRowStatus != ULONG_MAX); // row status must be there
|
|
_cbRowSize = RowMap.GetRowWidth();
|
|
|
|
//
|
|
// Be sure the alignment holds from row to row too.
|
|
//
|
|
if (maxAlignment &&
|
|
ALIGN(_cbRowSize, maxAlignment) != _cbRowSize) {
|
|
|
|
RowMap.ReserveRowSpace(
|
|
_cbRowSize,
|
|
ALIGN(_cbRowSize, maxAlignment) - _cbRowSize
|
|
);
|
|
|
|
_cbRowSize = RowMap.GetRowWidth();
|
|
Win4Assert(ALIGN(_cbRowSize, maxAlignment) == _cbRowSize);
|
|
}
|
|
|
|
Win4Assert(_cbRowSize >= sizeof (ULONG) && _cbRowSize < MAX_ROW_SIZE);
|
|
|
|
Win4Assert(TBL_PAGE_ALLOC_MIN > _cbRowSize);
|
|
_DataAllocator.SetRowSize(_cbRowSize);
|
|
|
|
tbDebugOut(( DEB_ITRACE,
|
|
"New CTableWindow %08x, Columns: %d\tRowSize: %d\n",
|
|
this, _Columns.Count(), _cbRowSize ));
|
|
|
|
_InitSortComparators();
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::~CTableWindow, public
|
|
//
|
|
// Synopsis: Destructor for a window. Deallocates any memory,
|
|
// and releases resources.
|
|
//
|
|
// Notes: All work is done by sub-object destructors
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CTableWindow::~CTableWindow(void)
|
|
{
|
|
tbDebugOut(( DEB_WINSPLIT, "Destroying Window 0x%X\n", GetSegId() ));
|
|
|
|
// Win4Assert(_cbHeapUsed == 0);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_AddColumnDesc, private
|
|
//
|
|
// Synopsis: Add a column description to a table
|
|
//
|
|
// Arguments: [MasterCol] - a reference to a master column description
|
|
// for the column to be added
|
|
// [RowMap] - a reference to a row allocation map for
|
|
// allocating row data
|
|
// [maxAlignment] - maximumn alignment constraint for the
|
|
// allocated row
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Effects: Space is allocated in the RowMap, maxAlignment is
|
|
// adjusted.
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void
|
|
CTableWindow::_AddColumnDesc(
|
|
CColumnMasterDesc& MasterCol,
|
|
CTableRowAlloc& RowMap,
|
|
unsigned& maxAlignment
|
|
)
|
|
{
|
|
VARTYPE vt = MasterCol.DataType;
|
|
if (vt == VT_NULL)
|
|
vt = VT_VARIANT;
|
|
|
|
//
|
|
// The data type VT_EMPTY is used for columns like bookmark which
|
|
// may occur in the master column set, but which has no data
|
|
// stored in the table. Don't even bother adding it to the table
|
|
// column set.
|
|
//
|
|
if (vt == VT_EMPTY)
|
|
return;
|
|
|
|
XPtr<CTableColumn> xTableCol( new CTableColumn( MasterCol.PropId, vt ) );
|
|
|
|
_cbHeapUsed += sizeof CTableColumn;
|
|
|
|
|
|
// If we can make this partial deferred, do so and get out
|
|
if ( _fCanPartialDefer && _CanPartialDeferCol( xTableCol ) )
|
|
{
|
|
|
|
tbDebugOut(( DEB_ITRACE,
|
|
"Setting col as partial deferred propid = %x\n",
|
|
xTableCol->GetPropId()));
|
|
|
|
xTableCol->SetPartialDeferred( TRUE );
|
|
|
|
_Columns.Add( xTableCol.GetPointer(), _Columns.Count() );
|
|
xTableCol.Acquire();
|
|
|
|
return;
|
|
}
|
|
|
|
USHORT cbData, cbAlignment, rgfFlags;
|
|
|
|
CTableVariant::VartypeInfo(vt, cbData, cbAlignment, rgfFlags);
|
|
Win4Assert(cbData != 0);
|
|
|
|
if (cbData == 0)
|
|
{
|
|
tbDebugOut(( DEB_WARN,
|
|
"Unknown variant type %4x for propid %d\n",
|
|
vt, MasterCol.PropId ));
|
|
}
|
|
|
|
if (MasterCol.IsCompressedCol())
|
|
{
|
|
//
|
|
// Global compression; save a pointer to the
|
|
// compressor.
|
|
//
|
|
|
|
xTableCol->SetGlobalCompressor( MasterCol.GetCompressor(),
|
|
MasterCol.GetCompressMasterId() );
|
|
|
|
if (xTableCol->GetCompressMasterId() != 0)
|
|
{
|
|
//
|
|
// Look up the masterID, point the data offset and width
|
|
// to it. Shadow the existing column.
|
|
//
|
|
|
|
BOOL fFound = FALSE;
|
|
CTableColumn* pColumn =
|
|
_Columns.Find(MasterCol.GetCompressMasterId(), fFound);
|
|
Win4Assert(fFound == TRUE);
|
|
|
|
xTableCol->SetValueField( vt,
|
|
pColumn->GetValueOffset(),
|
|
pColumn->GetValueSize() );
|
|
cbData = 0; // indicate no new data needed
|
|
|
|
Win4Assert( pColumn->IsStatusStored() );
|
|
|
|
xTableCol->SetStatusField( pColumn->GetStatusOffset(), sizeof (BYTE) );
|
|
}
|
|
else
|
|
{
|
|
cbData = cbAlignment = sizeof (ULONG);
|
|
}
|
|
}
|
|
|
|
if (cbAlignment)
|
|
{
|
|
if (cbAlignment > maxAlignment)
|
|
maxAlignment = cbAlignment;
|
|
}
|
|
else
|
|
{
|
|
cbAlignment = 1;
|
|
}
|
|
|
|
if (cbData != 0)
|
|
{
|
|
xTableCol->SetValueField( vt,
|
|
RowMap.AllocOffset( cbData, cbAlignment, TRUE ),
|
|
cbData);
|
|
|
|
// Always add a status byte -- even storage props may not be present
|
|
// for summary catalogs. For others, need to know if the value is
|
|
// deferred.
|
|
|
|
xTableCol->SetStatusField( RowMap.AllocOffset( sizeof (BYTE),
|
|
sizeof (BYTE),
|
|
TRUE ),
|
|
sizeof (BYTE));
|
|
}
|
|
|
|
if (xTableCol->PropId == pidWorkId)
|
|
{
|
|
_iOffsetWid = xTableCol->GetValueOffset();
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if (xTableCol->PropId == pidRank)
|
|
{
|
|
_iOffsetRank = xTableCol->GetValueOffset();
|
|
}
|
|
else if (xTableCol->PropId == pidHitCount)
|
|
{
|
|
_iOffsetHitCount = xTableCol->GetValueOffset();
|
|
}
|
|
else if (xTableCol->PropId == pidRowStatus)
|
|
{
|
|
_iOffsetRowStatus = xTableCol->GetValueOffset();
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if (xTableCol->PropId == pidChapter)
|
|
{
|
|
_iOffsetChapter = xTableCol->GetValueOffset();
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if (xTableCol->PropId == pidSelf)
|
|
{
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if ( xTableCol->IsCompressedCol() && 0 != xTableCol->GetCompressMasterId() )
|
|
{
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
|
|
#if CIDBG==1
|
|
if ( xTableCol->IsValueStored() )
|
|
Win4Assert( xTableCol->IsStatusStored() );
|
|
#endif
|
|
|
|
_Columns.Add( xTableCol.GetPointer(), _Columns.Count() );
|
|
xTableCol.Acquire();
|
|
} //_AddColumnDesc
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CTableWindow::_AddColumnDesc, private
|
|
//
|
|
// Synopsis: Add a column description to the table based on another
|
|
// column description.
|
|
//
|
|
// Arguments: [srcTableCol] - The source upon which the new column table
|
|
// description is based
|
|
// [RowMap] - a reference to a row allocation map for
|
|
// allocating row data
|
|
// [maxAlignment] - maximumn alignment constraint for the
|
|
// allocated row.
|
|
//
|
|
// History: 2-07-95 srikants Created
|
|
// 11-Nov-99 KLam size check only valid for non-compressed
|
|
// columns
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
void
|
|
CTableWindow::_AddColumnDesc(
|
|
CTableColumn& srcTableCol,
|
|
CTableRowAlloc& RowMap,
|
|
unsigned& maxAlignment
|
|
)
|
|
{
|
|
XPtr<CTableColumn> xTableCol( new CTableColumn(srcTableCol) );
|
|
|
|
_cbHeapUsed += sizeof CTableColumn;
|
|
|
|
// If partial deferred, just add and return
|
|
if ( xTableCol->IsPartialDeferred() )
|
|
{
|
|
_Columns.Add( xTableCol.GetPointer(), _Columns.Count() );
|
|
xTableCol.Acquire();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VARTYPE vt = srcTableCol.GetStoredType();
|
|
|
|
USHORT cbData, cbAlignment, rgfFlags;
|
|
|
|
CTableVariant::VartypeInfo(vt, cbData, cbAlignment, rgfFlags);
|
|
|
|
// Globally compressed columns either have a size of 0 or the size of the key
|
|
// in the column
|
|
Win4Assert( cbData != 0
|
|
|| ( srcTableCol.IsGlobalCompressedCol()
|
|
&& srcTableCol.GetCompressMasterId() ) );
|
|
Win4Assert( cbData == srcTableCol.GetValueSize()
|
|
|| srcTableCol.IsGlobalCompressedCol() );
|
|
|
|
if ( 0 == cbData )
|
|
{
|
|
tbDebugOut(( DEB_WARN,
|
|
"Unknown variant type %4x for propid %d\n",
|
|
vt, srcTableCol.GetPropId() ));
|
|
}
|
|
|
|
//
|
|
// Store strings by reference
|
|
//
|
|
if ( (rgfFlags & CTableVariant::ByRef) &&
|
|
!(rgfFlags & CTableVariant::StoreDirect))
|
|
{
|
|
Win4Assert( 0 == ( vt & VT_BYREF ) );
|
|
}
|
|
|
|
if ( srcTableCol.IsCompressedCol() )
|
|
{
|
|
if ( srcTableCol.IsGlobalCompressedCol() )
|
|
{
|
|
if ( 0 != srcTableCol.GetCompressMasterId() )
|
|
{
|
|
cbData = 0; // indicate no new data needed
|
|
}
|
|
else
|
|
{
|
|
// This size is refering to the size of the compression key
|
|
cbData = cbAlignment = sizeof (ULONG);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// There is local compression in the source table.
|
|
//
|
|
if ( (pidName == srcTableCol.PropId) || (pidPath == srcTableCol.PropId ) )
|
|
{
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// How did the local compression got set when it
|
|
// is not yet implemented.
|
|
//
|
|
Win4Assert( !"TableWindow - Local Compression - NYI " );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cbAlignment)
|
|
{
|
|
if (cbAlignment > maxAlignment)
|
|
maxAlignment = cbAlignment;
|
|
}
|
|
else
|
|
{
|
|
cbAlignment = 1;
|
|
}
|
|
|
|
//
|
|
// This SetValueField call is not necessary for setting the
|
|
// values in the column, since those have already been copied
|
|
// from the source column, but this has the important side-effect
|
|
// of updating the RowMap (which will presumably give back the
|
|
// same values as the original allocation).
|
|
//
|
|
if (cbData != 0)
|
|
{
|
|
xTableCol->SetValueField( vt,
|
|
RowMap.AllocOffset( cbData, cbAlignment, TRUE ),
|
|
cbData);
|
|
|
|
Win4Assert( xTableCol->GetValueOffset() == srcTableCol.GetValueOffset() );
|
|
|
|
// Always add a status byte -- even storage props may not be present
|
|
// for summary catalogs. For others, need to know if the value is
|
|
// deferred.
|
|
|
|
xTableCol->SetStatusField( RowMap.AllocOffset( sizeof (BYTE),
|
|
sizeof (BYTE),
|
|
TRUE ),
|
|
sizeof (BYTE));
|
|
}
|
|
|
|
if (xTableCol->PropId == pidWorkId)
|
|
{
|
|
_iOffsetWid = xTableCol->GetValueOffset();
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if (xTableCol->PropId == pidRank)
|
|
{
|
|
_iOffsetRank = xTableCol->GetValueOffset();
|
|
}
|
|
else if (xTableCol->PropId == pidHitCount)
|
|
{
|
|
_iOffsetHitCount = xTableCol->GetValueOffset();
|
|
}
|
|
else if (xTableCol->PropId == pidRowStatus)
|
|
{
|
|
_iOffsetRowStatus = xTableCol->GetValueOffset();
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if (xTableCol->PropId == pidChapter)
|
|
{
|
|
_iOffsetChapter = xTableCol->GetValueOffset();
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if (xTableCol->PropId == pidSelf)
|
|
{
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
else if ( xTableCol->IsCompressedCol() && 0 != xTableCol->GetCompressMasterId() )
|
|
{
|
|
xTableCol->MarkAsSpecial();
|
|
}
|
|
|
|
#if CIDBG==1
|
|
if ( xTableCol->IsValueStored() )
|
|
Win4Assert( xTableCol->IsStatusStored() );
|
|
#endif
|
|
|
|
_Columns.Add(xTableCol.GetPointer(), _Columns.Count());
|
|
xTableCol.Acquire();
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_PopulateRow, private
|
|
//
|
|
// Synopsis: Extracts row data from an object cursor into row storage
|
|
//
|
|
// Arguments: [obj] -- source of data
|
|
// [pThisRow] -- where to put the data
|
|
// [wid] -- workid of the row
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_PopulateRow(
|
|
CRetriever & obj,
|
|
BYTE * pThisRow,
|
|
WORKID wid )
|
|
{
|
|
Win4Assert(!(wid & 0x80000000)); // Bad work ID?
|
|
_SetRowWorkid(pThisRow, wid);
|
|
|
|
//
|
|
// Temporary buffer for column data. If it doesn't fit in this
|
|
// buffer, defer the load. The buffer is owned by CLargeTable
|
|
// and is only accessed under the lock taken in CLargeTable::PutRow
|
|
//
|
|
|
|
XUseSharedBuffer xSharedBuf(_sharedBuf);
|
|
XArray<BYTE> xBuf;
|
|
|
|
CTableVariant* pvarnt = (CTableVariant *) xSharedBuf.LokGetBuffer();
|
|
unsigned cbBuf = xSharedBuf.LokGetSize();
|
|
|
|
for (unsigned i=0; i < _Columns.Count(); i++)
|
|
{
|
|
CTableColumn* pColumn = _Columns[ i ];
|
|
|
|
// Ignore PartailDeferred columns here. Retrieve data only when
|
|
// requested by the client
|
|
if ( pColumn->IsPartialDeferred() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Win4Assert( pColumn->IsStatusStored() );
|
|
|
|
// Some columns have special processing and don't need to go through
|
|
// this loop.
|
|
|
|
if ( pColumn->IsSpecial() )
|
|
{
|
|
pColumn->SetStatus( pThisRow, CTableColumn::StoreStatusOK );
|
|
continue;
|
|
}
|
|
|
|
|
|
ULONG cbLength = cbBuf;
|
|
|
|
VARTYPE vt = pColumn->GetStoredType();
|
|
|
|
// it'd be a "special" column handled above if it were VT_EMPTY
|
|
Win4Assert( VT_EMPTY != vt );
|
|
|
|
GetValueResult eGvr = obj.GetPropertyValue( pColumn->PropId,
|
|
pvarnt,
|
|
&cbLength );
|
|
|
|
CTableColumn::StoreStatus stat = CTableColumn::StoreStatusOK;
|
|
|
|
switch ( eGvr )
|
|
{
|
|
case GVRSuccess:
|
|
break;
|
|
|
|
case GVRNotAvailable:
|
|
pvarnt->vt = VT_EMPTY;
|
|
stat = CTableColumn::StoreStatusNull;
|
|
break;
|
|
|
|
case GVRNotEnoughSpace:
|
|
{
|
|
tbDebugOut(( DEB_ITRACE,
|
|
"variant data too large for propid %d\n",
|
|
pColumn->PropId ));
|
|
|
|
stat = CTableColumn::StoreStatusDeferred;
|
|
|
|
if ( pColumn->IsLengthStored() )
|
|
* (ULONG *) (pThisRow + pColumn->GetLengthOffset()) = cbLength;
|
|
}
|
|
break;
|
|
|
|
case GVRSharingViolation:
|
|
pvarnt->vt = VT_EMPTY;
|
|
stat = CTableColumn::StoreStatusNull;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// There was an error while retrieving the column from the
|
|
// retriever.
|
|
//
|
|
THROW( CException(CRetriever::NtStatusFromGVR(eGvr)) );
|
|
break;
|
|
}
|
|
|
|
BYTE* pRowColDataBuf = pThisRow + pColumn->GetValueOffset();
|
|
ULONG cbRowColDataBuf = pColumn->GetValueSize();
|
|
|
|
RtlZeroMemory(pRowColDataBuf, cbRowColDataBuf);
|
|
|
|
Win4Assert( pColumn->IsStatusStored() );
|
|
|
|
if ( ( CTableColumn::StoreStatusOK == stat ) &&
|
|
( VT_EMPTY == pvarnt->vt ) )
|
|
pColumn->SetStatus( pThisRow, CTableColumn::StoreStatusNull );
|
|
else
|
|
pColumn->SetStatus( pThisRow, stat );
|
|
|
|
if ( CTableColumn::StoreStatusOK == stat )
|
|
{
|
|
if ( pColumn->IsLengthStored() )
|
|
* (ULONG *) (pThisRow + pColumn->GetLengthOffset()) = cbLength;
|
|
|
|
//
|
|
// Store the property value in the table. The main cases
|
|
// handled below are:
|
|
//
|
|
// 1. The column is compressed. In this case, the column
|
|
// compressor will handle it.
|
|
// 2. The column is stored as a variant. Just store the
|
|
// value, as long as it's not too big.
|
|
// 3. The column is stored as a data value, and the property
|
|
// value is of the same type. Just store the data value.
|
|
// 4. The column is stored as a data value, and the property
|
|
// value is of a different type. In this case, attempt to
|
|
// convert the value to another type and store it. Otherwise
|
|
// fail.
|
|
// BROKENCODE - failure here could be the wrong thing to do. Other
|
|
// things that could be done include converting the column
|
|
// (might be the correct thing to do if it's a range error
|
|
// on a column which has been range compressed), or storing
|
|
// the value as an exception (might be the correct thing to
|
|
// do when it is a column which has been type compressed).
|
|
//
|
|
|
|
if (pColumn->IsCompressedCol())
|
|
{
|
|
pColumn->GetCompressor()->AddData(pvarnt,
|
|
cbRowColDataBuf?
|
|
(ULONG *)pRowColDataBuf: 0,
|
|
eGvr);
|
|
}
|
|
else
|
|
{
|
|
DBLENGTH ulTemp;
|
|
pvarnt->CopyOrCoerce( pRowColDataBuf,
|
|
cbRowColDataBuf,
|
|
vt,
|
|
ulTemp,
|
|
_DataAllocator );
|
|
}
|
|
}
|
|
}
|
|
} //_PopulateRow
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::PutRow, public
|
|
//
|
|
// Synopsis: Add a row to a large table
|
|
//
|
|
// Arguments: [obj] -- a pointer to an accessor which can
|
|
// return object data
|
|
//
|
|
// Returns: FALSE -- Don't need progress indication
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
BOOL CTableWindow::PutRow( CRetriever& obj, CTableRowKey & currKey )
|
|
{
|
|
WORKID wid = obj.WorkId();
|
|
|
|
//
|
|
// NOTE: Even if in the initial fill, first check to see if the row
|
|
// is already in the table. We may see the same work ID
|
|
// multiple times due to links.
|
|
//
|
|
|
|
TBL_OFF obRow;
|
|
ULONG iRowOrdinal;
|
|
BOOL fExisting = _FindWorkId(wid, obRow, iRowOrdinal);
|
|
// Win4Assert( !fExisting &&
|
|
// "Modifications must be treated as deletions followed by additions" );
|
|
|
|
if ( fExisting )
|
|
{
|
|
BYTE * pbOldRow = _DataAllocator.FixedPointer( obRow );
|
|
if ( TBL_DATA_PENDING_DELETE != _RowStatus(pbOldRow) )
|
|
{
|
|
tbDebugOut(( DEB_WARN, "Not adding a linked object with wid 0x%X \n", wid ));
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// There can be a pending delete only if there is a watch
|
|
// region.
|
|
//
|
|
Win4Assert( IsWatched() );
|
|
}
|
|
}
|
|
|
|
BYTE * pThisRow = _RowAlloc();
|
|
|
|
Win4Assert(pThisRow != NULL);
|
|
|
|
_PopulateRow( obj, pThisRow, wid );
|
|
|
|
TBL_OFF oTableRow = _DataAllocator.FixedOffset(pThisRow);
|
|
|
|
CRowIndex & rowIndex = _GetInvisibleRowIndex();
|
|
|
|
ULONG iRowPos = rowIndex.AddRow(oTableRow);
|
|
|
|
tbDebugOut(( DEB_BOOKMARK,
|
|
"CTableWindow::PutRow Add new row Wid 0x%X -- Segment 0x%X -- oTable 0x%X \n",
|
|
wid, GetSegId(), oTableRow ));
|
|
|
|
_SetRowStatus(pThisRow,TBL_DATA_ROWADDED);
|
|
|
|
if ( !IsWatched() )
|
|
{
|
|
//
|
|
// There are no notifications enabled for this window. So, we
|
|
// should add the entry to the book mark mapping.
|
|
//
|
|
_BookMarkMap.AddReplaceBookMark( wid, oTableRow );
|
|
}
|
|
else
|
|
{
|
|
_xDeltaBookMarkMap->AddReplaceBookMark( wid, oTableRow );
|
|
}
|
|
|
|
if ( IsCategorized() )
|
|
{
|
|
CCategParams params = { wid, widInvalid, widInvalid,
|
|
chaptInvalid, chaptInvalid,
|
|
0, 0 };
|
|
|
|
if ( 0 != iRowPos )
|
|
{
|
|
BYTE * pb = (BYTE *) _DataAllocator.FixedPointer(
|
|
rowIndex.GetRow( iRowPos - 1) );
|
|
params.widPrev = RowWorkid( pb );
|
|
params.catPrev = _RowChapter( pb );
|
|
}
|
|
if ( (iRowPos + 1) != rowIndex.RowCount() )
|
|
{
|
|
BYTE * pb = (BYTE *) _DataAllocator.FixedPointer(
|
|
rowIndex.GetRow( iRowPos + 1) );
|
|
params.widNext = RowWorkid( pb );
|
|
params.catNext = _RowChapter( pb );
|
|
}
|
|
|
|
if ( ( chaptInvalid == params.catPrev ) ||
|
|
( params.catNext != params.catPrev ) )
|
|
{
|
|
// new row is not sandwiched between rows in same category,
|
|
// so come up with positive column # where the new row
|
|
// differs from its neighbors.
|
|
|
|
if ( widInvalid != params.widPrev )
|
|
params.icmpPrev = _CompareRows( rowIndex.GetRow( iRowPos ),
|
|
rowIndex.GetRow( iRowPos - 1 ));
|
|
if ( widInvalid != params.widNext )
|
|
params.icmpNext = _CompareRows( rowIndex.GetRow( iRowPos + 1),
|
|
rowIndex.GetRow( iRowPos ));
|
|
}
|
|
|
|
_SetChapter( pThisRow, LokCategorize( params ) );
|
|
}
|
|
|
|
// Update Low and High Keys, if necessary, based on the rowpos of the new row
|
|
|
|
BOOL fReady = FALSE;
|
|
|
|
if ( 0 == iRowPos )
|
|
{
|
|
// need to update lowkey
|
|
|
|
currKey.MakeReady();
|
|
fReady = TRUE;
|
|
_lowKey = currKey;
|
|
}
|
|
|
|
if ( RowCount() - 1 == iRowPos )
|
|
{
|
|
// need to update highKey
|
|
|
|
if ( !fReady )
|
|
currKey.MakeReady();
|
|
_highKey = currKey;
|
|
}
|
|
|
|
return FALSE; // no need for progress calculation
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_FindWorkId, public
|
|
//
|
|
// Synopsis: Find the row associated with a work ID
|
|
//
|
|
// Arguments: [wid] -- work ID to be found
|
|
// [obRow] - set to the row offset from the base of the
|
|
// row data if the workid was found
|
|
// [riRowOrdinal] - set to the ordinal row number if the
|
|
// workid was found (the row index ordinal)
|
|
//
|
|
// Returns: BOOL - TRUE if wid is represented in this window, FALSE
|
|
// otherwise
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::_FindWorkId(
|
|
WORKID wid,
|
|
TBL_OFF & obRow,
|
|
ULONG & riRowOrdinal
|
|
)
|
|
{
|
|
//
|
|
// A linear search is made in the dynamic row index based on whether
|
|
// an entry exists in the bookmark mapping or not. This will eliminate
|
|
// searches in cases where the wid does not exist in the window.
|
|
//
|
|
BOOL fFound = FALSE;
|
|
|
|
if ( !IsWatched() )
|
|
{
|
|
//
|
|
// The notifications are not enabled. The "visible" bookmark mapping
|
|
// is uptodate and has all the entries.
|
|
//
|
|
fFound = _BookMarkMap.FindBookMark( wid, obRow, riRowOrdinal );
|
|
}
|
|
else
|
|
{
|
|
BOOL fLookInDynRowIndex = FALSE;
|
|
//
|
|
// Since notifications are enabled, we must first look for the wid
|
|
// in the "delta" bookmark mapping and then in the "visible" bookmark
|
|
// mapping. If a "valid" bookmark mapping exists, then we must locate the
|
|
// row ordinal in the "dynamic" row index.
|
|
//
|
|
if ( _xDeltaBookMarkMap->FindBookMark(wid, obRow) )
|
|
{
|
|
fLookInDynRowIndex = TRUE;
|
|
}
|
|
else if ( _BookMarkMap.FindBookMark(wid, obRow) )
|
|
{
|
|
//
|
|
// If it is in the static book mark mapping, then only the
|
|
// rows with the TBL_DATA_OKAY are valid for further look up.
|
|
//
|
|
BYTE * pbRow = (BYTE *) _DataAllocator.FixedPointer(obRow);
|
|
fLookInDynRowIndex = TBL_DATA_OKAY == _RowStatus(pbRow);
|
|
}
|
|
|
|
if (fLookInDynRowIndex)
|
|
fFound = _dynRowIndex.FindRow(obRow, riRowOrdinal);
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// This is an expensive test. Re-add it if there are bugs
|
|
//
|
|
if ( !fFound )
|
|
{
|
|
CRowIndex & srchRowIndex = _GetInvisibleRowIndex();
|
|
|
|
for (unsigned i = 0; i < srchRowIndex.RowCount(); i++)
|
|
{
|
|
BYTE * pbRow = (BYTE *) _DataAllocator.FixedPointer(srchRowIndex.GetRow(i));
|
|
|
|
Win4Assert(0 != pbRow);
|
|
Win4Assert( _RowStatus(pbRow) != TBL_DATA_PENDING_DELETE &&
|
|
_RowStatus(pbRow) != TBL_DATA_SOFT_DELETE );
|
|
|
|
if (RowWorkid(pbRow) == wid)
|
|
{
|
|
Win4Assert( !"Must not be found" );
|
|
|
|
obRow = srchRowIndex.GetRow(i);
|
|
riRowOrdinal = i;
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
return fFound;
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CTableWindow::FindBookMark, private
|
|
//
|
|
// Synopsis: Locates the requested bookmark using the bookmark mapping.
|
|
//
|
|
// Arguments: [wid] -- WorkId to locate
|
|
// [obRow] -- set to the row offset from the base of the
|
|
// row data if the workid was found
|
|
// [riRowOrdinal] -- set to the ordinal row number if the
|
|
// workid was found (the row index ordinal)
|
|
//
|
|
// History: 11-30-94 srikants Created
|
|
//
|
|
// Notes: This method differs from the _FindWorkId in that this uses
|
|
// the book mark mapping and _FindWorkId does not. When
|
|
// notifications are enabled for this window, we add rows to the
|
|
// "dynamic" row index but do not add the corresponding entry
|
|
// to the book mark mapping. In that case, we cannot use the
|
|
// BookMarkMap to locate the work id.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::FindBookMark(
|
|
WORKID wid,
|
|
TBL_OFF & obRow,
|
|
ULONG & riRowOrdinal
|
|
)
|
|
{
|
|
|
|
BOOL fFound = FALSE;
|
|
|
|
if ( _visibleRowIndex.RowCount() > 0 )
|
|
{
|
|
if ( WORKID_TBLFIRST == wid )
|
|
{
|
|
riRowOrdinal = 0;
|
|
obRow = _visibleRowIndex.GetRow(riRowOrdinal);
|
|
fFound = TRUE;
|
|
}
|
|
else if ( WORKID_TBLLAST == wid )
|
|
{
|
|
riRowOrdinal = _visibleRowIndex.RowCount()-1;
|
|
obRow = _visibleRowIndex.GetRow(riRowOrdinal);
|
|
fFound = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fFound = _BookMarkMap.FindBookMark( wid, obRow, riRowOrdinal );
|
|
}
|
|
}
|
|
|
|
return fFound;
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::IsRowInSegment, inline
|
|
//
|
|
// Synopsis: Determine if a row for some work ID exists in the table.
|
|
//
|
|
// Arguments: [wid] -- work ID to be found
|
|
//
|
|
// Returns: BOOL - TRUE if wid is represented in this window, FALSE
|
|
// otherwise
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::IsRowInSegment(WORKID wid)
|
|
{
|
|
TBL_OFF iRowOffset;
|
|
ULONG iRow;
|
|
return _FindWorkId(wid, iRowOffset, iRow);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::GetRows
|
|
//
|
|
// Synopsis: Return a set of row data to the caller
|
|
//
|
|
// Arguments: [hRegion] - region to be expanded
|
|
// [widStart] - WORKID identifying first row to be
|
|
// transferred. If WORKID_TBLBEFOREFIRST or
|
|
// 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 next row to be transferred from this table.
|
|
// Can be used as 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 table segment at the end
|
|
// of the transfer, and not all requested rows were
|
|
// transferred.
|
|
// DB_S_BLOCKLIMITEDROWS if insufficient space is
|
|
// available in the pVarAllocator.
|
|
//
|
|
// Notes: Extends the watch region (if handle non-zero)
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
SCODE CTableWindow::GetRows(
|
|
HWATCHREGION hRegion,
|
|
WORKID widStart,
|
|
CI_TBL_CHAPT chapt,
|
|
ULONG & cRowsToGet,
|
|
CTableColumnSet const & rOutColumns,
|
|
CGetRowsParams & rGetParams,
|
|
WORKID& rwidLastRowTransferred
|
|
) {
|
|
|
|
tbDebugOut(( DEB_BOOKMARK, "CTableWindow::GetRows called with starting wid 0x%X\n", widStart ));
|
|
|
|
if (widStart == WORKID_TBLAFTERLAST)
|
|
{
|
|
rwidLastRowTransferred = WORKID_TBLAFTERLAST;
|
|
tbDebugOut(( DEB_BOOKMARK, "CTableWindow::GetRows returning with 0x%X\n",
|
|
DB_S_ENDOFROWSET ));
|
|
return DB_S_ENDOFROWSET;
|
|
}
|
|
|
|
if (widStart == widInvalid)
|
|
{
|
|
tbDebugOut(( DEB_BOOKMARK, "CTableWindow::GetRows returning with 0x%X\n", E_FAIL ));
|
|
return E_FAIL; // maybe the wid got deleted???
|
|
}
|
|
|
|
ULONG iRowIndex = 0; // Starting row as an index in the row index
|
|
|
|
CRowIndex * pRowIndex = _BookMarkMap.GetRowIndex();
|
|
Win4Assert( pRowIndex == &_visibleRowIndex );
|
|
|
|
if ( widStart == WORKID_TBLLAST )
|
|
iRowIndex = pRowIndex->RowCount() - 1;
|
|
|
|
if (widStart != WORKID_TBLFIRST
|
|
&& widStart != WORKID_TBLBEFOREFIRST
|
|
&& widStart != WORKID_TBLLAST )
|
|
{
|
|
TBL_OFF iRowOffset = 0; // Starting row as an offset into row data
|
|
BOOL fFound = FindBookMark(widStart, iRowOffset, iRowIndex);
|
|
|
|
if (!fFound)
|
|
return E_FAIL;
|
|
}
|
|
else if ( IsCategorized() && DB_NULL_HCHAPTER != chapt )
|
|
{
|
|
// turn special wids (first/last) into real wids in the chapter
|
|
|
|
if ( !rGetParams.GetFwdFetch() )
|
|
Win4Assert( !"Backwards fetch not yet implemented for categorized rowsets" );
|
|
|
|
TBL_OFF iRowOffset = 0;
|
|
if ( !_FindWorkId( GetCategorizer()->GetFirstWorkid( chapt ),
|
|
iRowOffset,
|
|
iRowIndex ) )
|
|
{
|
|
// must be a continuation of getrows from a previous window
|
|
iRowIndex = 0;
|
|
}
|
|
}
|
|
|
|
SCODE scResult = S_OK;
|
|
|
|
//
|
|
// iRowIndex is a ulong, and so after row 0 is fetched (in
|
|
// backwards fetch) we set fBwdRowsExhausted to true, instead
|
|
// decrementing 0, which is the value of iRowIndex.
|
|
// fBwdRowsExhausted is initialy true if there are 0 rows in this
|
|
// segment.
|
|
//
|
|
BOOL fBwdRowsExhausted = pRowIndex->RowCount() == 0;
|
|
|
|
//
|
|
// This flag is set to true, if rOutColumns contains at least one
|
|
// partially deferred column
|
|
//
|
|
BOOL fPartialDeferredCols = FALSE;
|
|
|
|
Win4Assert( 0 != pRowIndex );
|
|
|
|
TRY
|
|
{
|
|
const COUNT_CACHED_TABLE_COLS = 10; // Size of cached column array
|
|
CTableColumn *apTableCol[COUNT_CACHED_TABLE_COLS];
|
|
|
|
CTableColumn **pTableCols = apTableCol;
|
|
|
|
XArray<CTableColumn *> xaTableCol;
|
|
if ( rOutColumns.Count() > COUNT_CACHED_TABLE_COLS )
|
|
{
|
|
xaTableCol.Init( rOutColumns.Count() );
|
|
pTableCols = xaTableCol.GetPointer();
|
|
}
|
|
|
|
for (unsigned i=0; i < rOutColumns.Count(); i++)
|
|
{
|
|
CTableColumn const & outColumn = *rOutColumns[ i ];
|
|
|
|
BOOL fFound = FALSE;
|
|
pTableCols[i] = _Columns.Find( outColumn.PropId, fFound );
|
|
Win4Assert(fFound == TRUE);
|
|
|
|
fPartialDeferredCols = ( fPartialDeferredCols ||
|
|
pTableCols[i]->IsPartialDeferred() );
|
|
}
|
|
|
|
//
|
|
// FRowsRemaining tests whether there are rows remaining.
|
|
// The test differs for forward and backwards fetch.
|
|
//
|
|
|
|
BOOL fRowsRemaining;
|
|
if ( rGetParams.GetFwdFetch() )
|
|
fRowsRemaining = iRowIndex < pRowIndex->RowCount();
|
|
else
|
|
fRowsRemaining = !fBwdRowsExhausted;
|
|
|
|
//
|
|
// Set up the cursor in case we have partial deferred column(s)
|
|
//
|
|
|
|
BOOL fAbort = FALSE;
|
|
|
|
if ( fPartialDeferredCols && _xCursor.IsNull() )
|
|
{
|
|
_xCursor.Set( _QueryExecute.GetSingletonCursor( fAbort ) );
|
|
Win4Assert( _xCursor.GetPointer() );
|
|
}
|
|
|
|
while ( 0 != cRowsToGet && fRowsRemaining )
|
|
{
|
|
BYTE *pRow = (BYTE *)
|
|
_DataAllocator.FixedPointer(pRowIndex->GetRow(iRowIndex));
|
|
|
|
// If we've moved on to another chapter, stop retrieving rows
|
|
|
|
if ( IsCategorized() &&
|
|
DB_NULL_HCHAPTER != chapt &&
|
|
(_RowChapter(pRow) != chapt) )
|
|
{
|
|
scResult = DB_S_ENDOFROWSET;
|
|
break;
|
|
}
|
|
|
|
BYTE* pbOutRow = (BYTE *)rGetParams.GetRowBuffer();
|
|
|
|
//
|
|
// Update RowId in xCursor if we have partial deferred column(s)
|
|
//
|
|
|
|
if ( fPartialDeferredCols )
|
|
{
|
|
_xCursor->SetCurrentWorkId( RowWorkid( pRow ) );
|
|
Win4Assert( _xCursor->WorkId() != widInvalid );
|
|
}
|
|
|
|
_CopyColumnData( pRow,
|
|
pbOutRow,
|
|
pTableCols,
|
|
rOutColumns,
|
|
rGetParams.GetVarAllocator(),
|
|
_xCursor.GetPointer() );
|
|
|
|
//
|
|
// We've transferred a row. Update pointers and counters.
|
|
//
|
|
|
|
rwidLastRowTransferred = RowWorkid((BYTE *)_DataAllocator.
|
|
FixedPointer(pRowIndex->GetRow(iRowIndex)));
|
|
|
|
if ( rGetParams.GetFwdFetch() )
|
|
iRowIndex++;
|
|
else
|
|
{
|
|
if ( iRowIndex == 0 )
|
|
fBwdRowsExhausted = TRUE;
|
|
else
|
|
iRowIndex--;
|
|
}
|
|
|
|
rGetParams.IncrementRowCount();
|
|
cRowsToGet--;
|
|
|
|
if ( rGetParams.GetFwdFetch() )
|
|
fRowsRemaining = iRowIndex < pRowIndex->RowCount();
|
|
else
|
|
fRowsRemaining = !fBwdRowsExhausted;
|
|
}
|
|
}
|
|
|
|
CATCH( CException, e )
|
|
{
|
|
scResult = e.GetErrorCode();
|
|
|
|
if ( STATUS_BUFFER_TOO_SMALL == scResult &&
|
|
rGetParams.RowsTransferred() > 0 )
|
|
scResult = DB_S_BLOCKLIMITEDROWS;
|
|
}
|
|
END_CATCH
|
|
|
|
if (! _xCursor.IsNull() )
|
|
_xCursor->Quiesce( );
|
|
|
|
if (iRowIndex >= pRowIndex->RowCount() || fBwdRowsExhausted )
|
|
{
|
|
Win4Assert(scResult != DB_S_BLOCKLIMITEDROWS);
|
|
tbDebugOut(( DEB_BOOKMARK, "CTableWindow::GetRows End of table window\n" ));
|
|
return DB_S_ENDOFROWSET;
|
|
}
|
|
else
|
|
{
|
|
tbDebugOut(( DEB_BOOKMARK, "CTableWindow::GetRows next wid 0x%X\n", rwidLastRowTransferred ));
|
|
return scResult;
|
|
}
|
|
} //GetRows
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_CopyColumnData, private
|
|
//
|
|
// Synopsis: Goes thru the list of output columns and transfers
|
|
// data to pbOutRow for them. The input data either comes
|
|
// from pbSrcRow or in case of partially deferred column
|
|
// we retrive it from the CRetriever object
|
|
//
|
|
// Arguments: [pSrcRow] -- the src row
|
|
// [pThisRow] -- where to put the data
|
|
// [pTableCols] -- table(input) column set corr. to output columns
|
|
// [rOutColumns] -- output column set
|
|
// [rDstPool] -- destination variable data allocator
|
|
// [obj] -- source of data
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_CopyColumnData(
|
|
BYTE* pbSrcRow,
|
|
BYTE* pbOutRow,
|
|
CTableColumn **pTableCols,
|
|
CTableColumnSet const& rOutColumns,
|
|
PVarAllocator& rDstPool,
|
|
CRetriever* obj /* = NULL */ )
|
|
{
|
|
|
|
//
|
|
// Temporary buffer for column data. If it doesn't fit in this
|
|
// buffer, defer the load. The buffer is owned by CLargeTable.
|
|
// and is only accessed under the lock taken in CLargeTable::GetRowsAt
|
|
//
|
|
XUseSharedBuffer xSharedBuf(_sharedBuf);
|
|
CTableVariant* pvarnt = (CTableVariant *) xSharedBuf.LokGetBuffer();
|
|
unsigned cbBuf = xSharedBuf.LokGetSize();
|
|
|
|
for (unsigned i=0; i < rOutColumns.Count(); i++)
|
|
{
|
|
CTableColumn * pColumn = pTableCols[i];
|
|
Win4Assert(pColumn);
|
|
|
|
CTableColumn* pOutColumn = rOutColumns[ i ];
|
|
Win4Assert(pOutColumn);
|
|
|
|
if ( !pColumn->IsPartialDeferred() )
|
|
{
|
|
pColumn->CopyColumnData( pbOutRow,
|
|
*pOutColumn,
|
|
rDstPool,
|
|
pbSrcRow,
|
|
_DataAllocator );
|
|
continue;
|
|
}
|
|
|
|
|
|
ULONG cbLength = cbBuf;
|
|
|
|
VARTYPE vt = pOutColumn->GetStoredType();
|
|
|
|
Win4Assert( obj );
|
|
|
|
GetValueResult eGvr = obj->GetPropertyValue( pColumn->PropId,
|
|
pvarnt,
|
|
&cbLength );
|
|
|
|
CTableColumn::StoreStatus stat = CTableColumn::StoreStatusOK;
|
|
|
|
switch ( eGvr )
|
|
{
|
|
case GVRSuccess:
|
|
break;
|
|
|
|
case GVRNotAvailable:
|
|
pvarnt->vt = VT_EMPTY;
|
|
stat = CTableColumn::StoreStatusNull;
|
|
break;
|
|
|
|
case GVRNotEnoughSpace:
|
|
{
|
|
tbDebugOut(( DEB_ITRACE,
|
|
"variant data too large for propid %d\n",
|
|
pColumn->PropId ));
|
|
|
|
stat = CTableColumn::StoreStatusDeferred;
|
|
|
|
if ( pOutColumn->IsLengthStored() )
|
|
* (ULONG *) (pbOutRow + pOutColumn->GetLengthOffset()) = cbLength;
|
|
}
|
|
break;
|
|
|
|
case GVRSharingViolation:
|
|
pvarnt->vt = VT_EMPTY;
|
|
stat = CTableColumn::StoreStatusNull;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// There was an error while retrieving the column from the
|
|
// retriever.
|
|
THROW( CException(CRetriever::NtStatusFromGVR(eGvr)) );
|
|
break;
|
|
}
|
|
|
|
BYTE* pRowColDataBuf = pbOutRow + pOutColumn->GetValueOffset();
|
|
ULONG cbRowColDataBuf = pOutColumn->GetValueSize();
|
|
|
|
RtlZeroMemory(pRowColDataBuf, cbRowColDataBuf);
|
|
|
|
Win4Assert( pOutColumn->IsStatusStored() );
|
|
|
|
if ( ( CTableColumn::StoreStatusOK == stat ) &&
|
|
( VT_EMPTY == pvarnt->vt ) )
|
|
pOutColumn->SetStatus( pbOutRow, CTableColumn::StoreStatusNull );
|
|
else
|
|
pOutColumn->SetStatus( pbOutRow, stat );
|
|
|
|
if ( CTableColumn::StoreStatusOK == stat )
|
|
{
|
|
if ( pOutColumn->IsLengthStored() )
|
|
* (ULONG *) (pbOutRow + pOutColumn->GetLengthOffset()) = cbLength;
|
|
|
|
if (pOutColumn->IsCompressedCol())
|
|
{
|
|
pOutColumn->GetCompressor()->AddData(pvarnt,
|
|
cbRowColDataBuf?
|
|
(ULONG *)pRowColDataBuf: 0,
|
|
eGvr);
|
|
}
|
|
else
|
|
{
|
|
DBLENGTH ulTemp;
|
|
pvarnt->CopyOrCoerce( pRowColDataBuf,
|
|
cbRowColDataBuf,
|
|
vt,
|
|
ulTemp,
|
|
rDstPool );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::RemoveRow, public
|
|
//
|
|
// Synopsis: Removes a row from the table, if it exists.
|
|
//
|
|
// Arguments: [varUnique] -- Variant that uniquely identifies the row.
|
|
// [widNext] -- Returns workid of row immediately after
|
|
// deleted row, used for categorization.
|
|
// [chapt] -- Returns chapter of deleted row.
|
|
//
|
|
// Returns: TRUE if the row existed or FALSE otherwise
|
|
//
|
|
// History: 24 Oct 1994 dlee Created
|
|
//
|
|
// BROKENCODE/NEWFEATURE: update min/max wid? This code won't be called...
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::RemoveRow(
|
|
PROPVARIANT const & varUnique,
|
|
WORKID & widNext,
|
|
CI_TBL_CHAPT & chapt )
|
|
{
|
|
Win4Assert(varUnique.vt == VT_I4);
|
|
|
|
WORKID wid = (WORKID) varUnique.lVal;
|
|
|
|
TBL_OFF obRow;
|
|
ULONG iRowOrdinal;
|
|
BOOL fExisting = _FindWorkId(wid, obRow, iRowOrdinal);
|
|
|
|
if (fExisting)
|
|
{
|
|
BOOL fFirstRow = (0 == iRowOrdinal);
|
|
BOOL fLastRow = (RowCount() - 1 == iRowOrdinal);
|
|
|
|
CRowIndex & srchRowIndex = _GetInvisibleRowIndex();
|
|
|
|
BYTE * pRow = (BYTE *) _DataAllocator.FixedPointer(obRow);
|
|
|
|
if ( IsCategorized() )
|
|
{
|
|
chapt = _RowChapter( pRow );
|
|
|
|
if ( iRowOrdinal < ( srchRowIndex.RowCount() - 1 ) )
|
|
widNext = RowWorkid( _DataAllocator.FixedPointer(
|
|
srchRowIndex.GetRow(iRowOrdinal + 1)));
|
|
else
|
|
widNext = widInvalid;
|
|
}
|
|
|
|
const ULONG rowStatus = _RowStatus(pRow);
|
|
|
|
if ( TBL_DATA_ROWADDED == rowStatus || !IsWatched() )
|
|
{
|
|
//
|
|
// This row has only been added but not notified to the
|
|
// user. So, we can go ahead and do a hard delete.
|
|
//
|
|
BOOL fFound = FALSE;
|
|
if ( IsWatched() )
|
|
{
|
|
fFound = _xDeltaBookMarkMap->DeleteBookMark( wid );
|
|
}
|
|
else
|
|
{
|
|
Win4Assert( _xDeltaBookMarkMap.IsNull() ||
|
|
!_xDeltaBookMarkMap->IsBookMarkPresent(wid) );
|
|
Win4Assert( 0 == _dynRowIndex.RowCount() );
|
|
fFound = _BookMarkMap.DeleteBookMark( wid );
|
|
}
|
|
|
|
Win4Assert( fFound && "BookMark to be deleted not found" );
|
|
|
|
Win4Assert( obRow == srchRowIndex.GetRow(iRowOrdinal) );
|
|
srchRowIndex.DeleteRow(iRowOrdinal);
|
|
|
|
_cRowsHardDeleted++;
|
|
_SetRowStatus(pRow,TBL_DATA_HARD_DELETE);
|
|
|
|
tbDebugOut(( DEB_WATCH,
|
|
"Hard Deleting Workid 0x%X oTable %#p oIndex 0x%X\n",
|
|
wid, obRow, iRowOrdinal ));
|
|
|
|
// Update low and high keys
|
|
|
|
if ( fFirstRow && fLastRow )
|
|
{
|
|
// this means now RowCount == 0
|
|
// we should probably delete this segment
|
|
}
|
|
else if ( fFirstRow )
|
|
{
|
|
// the first row got deleted, therefore update lowKey
|
|
GetSortKey( 0, _lowKey );
|
|
}
|
|
else if ( fLastRow )
|
|
{
|
|
// the last row got deleted, therefore update highKey
|
|
GetSortKey( (ULONG) ( RowCount() - 1 ), _highKey );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The existence of this row is known to the user. It is left in
|
|
// a pending delete state until a refresh is done.
|
|
// Updation of _highKey and _lowKey is also done at that time
|
|
Win4Assert( _BookMarkMap.IsBookMarkPresent(wid) );
|
|
Win4Assert( _xDeltaBookMarkMap.IsNull() ||
|
|
!_xDeltaBookMarkMap->IsBookMarkPresent(wid) );
|
|
|
|
_cPendingDeletes++;
|
|
_SetRowStatus(pRow,TBL_DATA_PENDING_DELETE);
|
|
|
|
tbDebugOut(( DEB_WATCH,
|
|
"Soft Deleting Workid 0x%X oTable %#p oIndex 0x%X\n",
|
|
wid, obRow, iRowOrdinal ));
|
|
}
|
|
}
|
|
|
|
return fExisting;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::IsPendingDelete
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [wid] -
|
|
//
|
|
// Returns:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// History: 8-01-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::IsPendingDelete( WORKID wid )
|
|
{
|
|
TBL_OFF iRowOffset;
|
|
|
|
BOOL fFound = _BookMarkMap.FindBookMark( wid, iRowOffset );
|
|
if ( fFound )
|
|
{
|
|
BYTE * pbRow = (BYTE *) _DataAllocator.FixedPointer(iRowOffset);
|
|
return TBL_DATA_PENDING_DELETE == _RowStatus(pbRow) ;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::CompareRange, public
|
|
//
|
|
// Synopsis: Checks to see how a given potential row object would fit
|
|
// in this window based on the current sort criteria.
|
|
//
|
|
// Arguments: [obj] -- accessor to the potential row object
|
|
//
|
|
// Returns: -1 if < min item
|
|
// 0 if (<= max and >= min) or no items in window
|
|
// 1 if > max item
|
|
//
|
|
// History: 2 Sep 1994 dlee Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void
|
|
CTableWindow::CompareRange( CRetriever &obj, CCompareResult & rslt )
|
|
{
|
|
|
|
//
|
|
// If there is a comparator and any rows for comparison
|
|
//
|
|
|
|
CRowIndex & srchRowIndex = _GetInvisibleRowIndex();
|
|
|
|
if ((0 != _RowCompare.GetPointer()) &&
|
|
(0 != srchRowIndex.RowCount()))
|
|
{
|
|
|
|
int iComp = _RowCompare->CompareObject(obj,srchRowIndex.GetRow(0));
|
|
|
|
if (iComp > 0)
|
|
{
|
|
//
|
|
// Compare to the maximum row
|
|
// If > min and < max, it belongs in the range
|
|
//
|
|
|
|
iComp = _RowCompare->CompareObject(obj,
|
|
srchRowIndex.GetRow(srchRowIndex.RowCount()-1));
|
|
|
|
if (iComp < 0)
|
|
iComp = 0;
|
|
}
|
|
|
|
rslt.Set( iComp );
|
|
}
|
|
else
|
|
{
|
|
rslt.Set( CCompareResult::eUnknown );
|
|
}
|
|
|
|
} //CompareRange
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_InitSortComparators, private
|
|
//
|
|
// Synopsis: Establishes the sort order used by the table.
|
|
//
|
|
// History: 19 Jan 1995 dlee Created from old SetSortOrderCode
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_InitSortComparators()
|
|
{
|
|
Win4Assert( 0 == _RowCompare.GetPointer() &&
|
|
0 == _QuickRowCompare.GetPointer() );
|
|
|
|
Win4Assert( 0 != _pSortSet->Count() );
|
|
|
|
// Special-case quick comparators for one or two comparisons
|
|
// of basic types.
|
|
|
|
if (_pSortSet->Count() == 1)
|
|
{
|
|
SSortKey Key = _pSortSet->Get(0);
|
|
BOOL fFound = FALSE;
|
|
|
|
CTableColumn* pColumn = _Columns.Find(Key.pidColumn,fFound);
|
|
|
|
Win4Assert(fFound);
|
|
|
|
//
|
|
// If inline, not compressed, and not a vector, it is a
|
|
// candidate for a faster comparison.
|
|
//
|
|
// pidWorkid can be compressed and stored in the row for
|
|
// the downlevel case.
|
|
|
|
if ((pColumn->PropId == pidWorkId ||
|
|
! pColumn->IsCompressedCol()) &&
|
|
(0 == (pColumn->GetStoredType() & VT_VECTOR)))
|
|
{
|
|
if (pColumn->GetStoredType() == VT_I8 ||
|
|
pColumn->GetStoredType() == VT_FILETIME)
|
|
_QuickRowCompare.Set( new TRowCompare<LONGLONG>
|
|
(_DataAllocator,
|
|
pColumn->GetValueOffset(),
|
|
Key.dwOrder ));
|
|
else if (pColumn->GetStoredType() == VT_I4)
|
|
_QuickRowCompare.Set( new TRowCompare<LONG>
|
|
(_DataAllocator,
|
|
pColumn->GetValueOffset(),
|
|
Key.dwOrder ));
|
|
}
|
|
}
|
|
else if (_pSortSet->Count() == 2)
|
|
{
|
|
SSortKey Key1 = _pSortSet->Get(0);
|
|
SSortKey Key2 = _pSortSet->Get(1);
|
|
BOOL fFound = FALSE;
|
|
|
|
CTableColumn* pColumn1 = _Columns.Find(Key1.pidColumn,fFound);
|
|
Win4Assert(fFound);
|
|
|
|
CTableColumn* pColumn2 = _Columns.Find(Key2.pidColumn,fFound);
|
|
Win4Assert(fFound);
|
|
|
|
//
|
|
// If inline, not compressed, and not a vector, it is a
|
|
// candidate for a faster comparison.
|
|
//
|
|
// pidWorkid can be compressed and stored in the row for
|
|
// the downlevel case.
|
|
|
|
if ((! pColumn1->IsCompressedCol()) &&
|
|
(0 == (pColumn1->GetStoredType() & VT_VECTOR)) &&
|
|
(! pColumn2->IsCompressedCol()
|
|
|| pColumn2->PropId == pidWorkId
|
|
) &&
|
|
(0 == (pColumn2->GetStoredType() & VT_VECTOR)))
|
|
{
|
|
if (((pColumn1->GetStoredType() == VT_I8) ||
|
|
(pColumn1->GetStoredType() == VT_FILETIME)) &&
|
|
(pColumn2->GetStoredType() == VT_I4))
|
|
_QuickRowCompare.Set( new TRowCompareTwo<LONGLONG,LONG>
|
|
(_DataAllocator,
|
|
pColumn1->GetValueOffset(),
|
|
Key1.dwOrder,
|
|
pColumn2->GetValueOffset(),
|
|
Key2.dwOrder ));
|
|
else if ((pColumn1->GetStoredType() == VT_I4) &&
|
|
(pColumn2->GetStoredType() == VT_I4))
|
|
_QuickRowCompare.Set( new TRowCompareTwo<LONG,LONG>
|
|
(_DataAllocator,
|
|
pColumn1->GetValueOffset(),
|
|
Key1.dwOrder,
|
|
pColumn2->GetValueOffset(),
|
|
Key2.dwOrder ));
|
|
else if ((pColumn1->GetStoredType() == VT_UI4) &&
|
|
(pColumn2->GetStoredType() == VT_I4))
|
|
_QuickRowCompare.Set( new TRowCompareTwo<ULONG,LONG>
|
|
(_DataAllocator,
|
|
pColumn1->GetValueOffset(),
|
|
Key1.dwOrder,
|
|
pColumn2->GetValueOffset(),
|
|
Key2.dwOrder ));
|
|
else if ((pColumn1->GetStoredType() == VT_LPWSTR) &&
|
|
(pColumn2->GetStoredType() == VT_I4))
|
|
_QuickRowCompare.Set( new TRowCompareStringPlus<LONG>
|
|
(_DataAllocator,
|
|
pColumn1->GetValueOffset(),
|
|
Key1.dwOrder,
|
|
pColumn2->GetValueOffset(),
|
|
Key2.dwOrder ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make the default comparator
|
|
//
|
|
|
|
_RowCompare.Set( new CRowCompareVariant(*this) );
|
|
|
|
//
|
|
// Use a faster comparator if available, or just use the default
|
|
//
|
|
|
|
_dynRowIndex.SetComparator((0 != _QuickRowCompare.GetPointer()) ?
|
|
_QuickRowCompare.GetPointer() : _RowCompare.GetPointer());
|
|
|
|
_visibleRowIndex.SetComparator((0 != _QuickRowCompare.GetPointer()) ?
|
|
_QuickRowCompare.GetPointer() : _RowCompare.GetPointer());
|
|
|
|
} //_InitSortComparators
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::SortOrder, private
|
|
//
|
|
// Synopsis: Only here because of class hierarchy artifact.
|
|
//
|
|
// Returns: CSortSet & -- sort set from parent object.
|
|
//
|
|
// History: 24 Oct 1994 dlee Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CSortSet const & CTableWindow::SortOrder()
|
|
{
|
|
return * _pSortSet;
|
|
} //SortOrder
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_ReconcileRowIndexes
|
|
//
|
|
// Synopsis: Reconciles the source and destination row indexes. At the end
|
|
// of it both the source and destination will be identical. Also,
|
|
// all the deleted rows would have been removed from the row
|
|
// indexes.
|
|
//
|
|
// Arguments: [src] -
|
|
// [dst] -
|
|
//
|
|
// History: 7-31-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_ReconcileRowIndexes( CRowIndex & dst , CRowIndex & src )
|
|
{
|
|
ULONG cRowsInSrc = src.RowCount();
|
|
|
|
dst.ResizeAndInit( cRowsInSrc );
|
|
for ( unsigned i = 0; i < cRowsInSrc ; i++ )
|
|
{
|
|
TBL_OFF oRow = src.GetRow(i);
|
|
BYTE * pbRow = _GetRow( oRow );
|
|
const ULONG rowStatus = _RowStatus( pbRow );
|
|
|
|
//
|
|
// By the time reconcile is called, all pending deletes must be
|
|
// converted to hard deletes.
|
|
//
|
|
Win4Assert( TBL_DATA_PENDING_DELETE != rowStatus );
|
|
|
|
if ( TBL_DATA_OKAY == rowStatus )
|
|
{
|
|
dst.AppendRow( oRow );
|
|
}
|
|
else
|
|
{
|
|
tbDebugOut(( DEB_BOOKMARK,
|
|
"Not Copying Row 0x%X because of status 0x%X\n",
|
|
oRow, rowStatus ));
|
|
}
|
|
}
|
|
|
|
src.SyncUp( dst );
|
|
|
|
// Update Low and High Keys
|
|
if ( dst.RowCount() )
|
|
{
|
|
GetSortKey( 0, _lowKey );
|
|
GetSortKey( dst.RowCount() - 1, _highKey );
|
|
}
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_CleanupAndReconcileRowIndexes
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [fCreatingWatch] -
|
|
// [pScript] -
|
|
//
|
|
// Returns:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// History: 7-27-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_CleanupAndReconcileRowIndexes( BOOL fCreatingWatch )
|
|
{
|
|
|
|
|
|
Win4Assert( !_fSplitInProgress );
|
|
|
|
//
|
|
// First see how many rows have notification information
|
|
//
|
|
|
|
ULONG cAllocatedRows = _AllocatedRowCount();
|
|
|
|
BYTE *pbRow = (BYTE *) _DataAllocator.FirstRow();
|
|
|
|
ULONG cAdd = 0, cDelete = 0;
|
|
|
|
for (unsigned i = 0; i < cAllocatedRows; i++, pbRow += _cbRowSize)
|
|
{
|
|
switch (_RowStatus(pbRow))
|
|
{
|
|
case TBL_DATA_ROWADDED :
|
|
cAdd++;
|
|
break;
|
|
case TBL_DATA_PENDING_DELETE :
|
|
cDelete++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Record the notification info
|
|
//
|
|
|
|
ULONG cChanges = cAdd + cDelete;
|
|
ULONG iChange = 0;
|
|
|
|
for (i = 0, pbRow = (BYTE *) _DataAllocator.FirstRow();
|
|
iChange < cChanges && i < cAllocatedRows;
|
|
i++, pbRow += _cbRowSize)
|
|
{
|
|
|
|
ULONG currStatus = _RowStatus(pbRow);
|
|
switch (currStatus)
|
|
{
|
|
case TBL_DATA_ROWADDED :
|
|
|
|
//
|
|
// Update the book mark mapping to reflect the new row if
|
|
// notifications were enabled before this.
|
|
//
|
|
if ( !fCreatingWatch )
|
|
{
|
|
_BookMarkMap.AddReplaceBookMark( RowWorkid(pbRow),
|
|
_DataAllocator.FixedOffset(pbRow) );
|
|
}
|
|
|
|
|
|
_SetRowStatus(pbRow, TBL_DATA_OKAY);
|
|
tbDebugOut(( DEB_WATCH,
|
|
"Notify:Add Wid 0x%X TblRow0x%X\n",
|
|
RowWorkid(pbRow), _DataAllocator.FixedOffset(pbRow) ));
|
|
iChange++;
|
|
break;
|
|
|
|
case TBL_DATA_PENDING_DELETE :
|
|
|
|
Win4Assert( !fCreatingWatch );
|
|
|
|
//
|
|
// Mark the row so that it is considered a hard delete and
|
|
// also remove it from the bookmark mapping.
|
|
//
|
|
_SetRowStatus(pbRow,TBL_DATA_HARD_DELETE);
|
|
_BookMarkMap.DeleteBookMark( RowWorkid(pbRow) );
|
|
|
|
_cPendingDeletes--;
|
|
_cRowsHardDeleted++;
|
|
|
|
tbDebugOut(( DEB_WATCH,
|
|
"Notify:Delete Wid 0x%X TblRow0x%X\n",
|
|
RowWorkid(pbRow), _DataAllocator.FixedOffset(pbRow) ));
|
|
|
|
iChange++;
|
|
break;
|
|
}
|
|
|
|
|
|
#if DBG==1
|
|
//
|
|
// This is a very expensive test. Turn it off once code stabilizes.
|
|
//
|
|
{
|
|
//
|
|
// If any row in the tablewindow has a stats of TBL_DATA_OKAY, it
|
|
// must have an entry in the bookmark map now.
|
|
//
|
|
TBL_OFF oTableRow;
|
|
ULONG status = _RowStatus(pbRow);
|
|
|
|
if ( TBL_DATA_OKAY == status )
|
|
{
|
|
BOOL fFound = _BookMarkMap.FindBookMark( RowWorkid(pbRow),
|
|
oTableRow );
|
|
if ( !fFound )
|
|
{
|
|
_BookMarkMap.FindBookMark( RowWorkid(pbRow),
|
|
oTableRow );
|
|
Win4Assert( !"Not Found in BookMarkMap" );
|
|
}
|
|
TBL_OFF tbRow = _DataAllocator.FixedOffset(pbRow);
|
|
if ( tbRow != oTableRow )
|
|
{
|
|
Win4Assert( !"Wrong Data in BookMarkMap" );
|
|
}
|
|
}
|
|
}
|
|
#endif // DBG
|
|
}
|
|
|
|
//
|
|
// We should have processed all the changes.
|
|
//
|
|
Win4Assert( iChange == cChanges );
|
|
Win4Assert( 0 == _cPendingDeletes );
|
|
|
|
//
|
|
// Synchronize the static and dynamic row indexes.
|
|
//
|
|
if ( fCreatingWatch )
|
|
{
|
|
//
|
|
// Watch is being created for the first time. Selectively copy
|
|
// only the non-soft deleted rows from the visible row index to
|
|
// the dynamic row index.
|
|
//
|
|
_ReconcileRowIndexes( _dynRowIndex , _visibleRowIndex );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Reconcile the dynamic row index to the visible row index and
|
|
// then get rid of the soft deletion entries from the dynamic
|
|
// row index.
|
|
//
|
|
_ReconcileRowIndexes( _visibleRowIndex , _dynRowIndex );
|
|
}
|
|
|
|
_xDeltaBookMarkMap->DeleteAllEntries();
|
|
|
|
|
|
#if DBG==1
|
|
//
|
|
// This is a very expensive test. Take it out once code stabilizes.
|
|
//
|
|
for ( unsigned j = 0; j < _visibleRowIndex.RowCount(); j++ )
|
|
{
|
|
TBL_OFF oTableRow = _visibleRowIndex.GetRow(j);
|
|
BYTE * pbRow = (BYTE *) _DataAllocator.FixedPointer(oTableRow);
|
|
WORKID wid = RowWorkid(pbRow);
|
|
TBL_OFF bmTableRow;
|
|
ULONG bmIndex;
|
|
BOOL fFound = _BookMarkMap.FindBookMark( wid, bmTableRow, bmIndex );
|
|
if ( !fFound )
|
|
{
|
|
_BookMarkMap.FindBookMark( RowWorkid(pbRow), bmTableRow, bmIndex );
|
|
tbDebugOut(( DEB_ERROR,
|
|
"Workid 0x%X Not Found In BookMarkMap\n", wid ));
|
|
Win4Assert( !"Verification:Not Found in BookMarkMap" );
|
|
}
|
|
|
|
if ( bmTableRow != oTableRow || bmIndex != j )
|
|
{
|
|
tbDebugOut(( DEB_ERROR,
|
|
"Mismatch in BookMarkMap oRow=%p:iRowIndex=%x ",
|
|
bmTableRow, bmIndex ));
|
|
tbDebugOut(( DEB_ERROR|DEB_NOCOMPNAME,
|
|
"and RowIndex oRow=%p:iRowIndex=%x\n",
|
|
oTableRow, j ));
|
|
|
|
Win4Assert( !"Verification:Wrong Data in BookMarkMap" );
|
|
}
|
|
}
|
|
#endif // DBG==1
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_StartStaticDynamicSplit
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// History: 7-27-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_StartStaticDynamicSplit()
|
|
{
|
|
_CleanupAndReconcileRowIndexes(TRUE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_EndStaticDynamicSplit
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// History: 7-27-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_EndStaticDynamicSplit()
|
|
{
|
|
_CleanupAndReconcileRowIndexes(FALSE);
|
|
_dynRowIndex.ClearAll();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_Refresh
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// History: 7-27-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_Refresh()
|
|
{
|
|
_CleanupAndReconcileRowIndexes(FALSE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Method: CTableWindow::_CopyRow
|
|
//
|
|
// Synopsis: Given a source window and a row offset in the source window,
|
|
// this method makes a copy of the source row and adds it to the
|
|
// current window.
|
|
//
|
|
// Arguments: [srcWindow] - Reference to the source window.
|
|
// [oSrcTableRow] - Offset in the source window.
|
|
//
|
|
// Returns: The offset of the row added in this window.
|
|
//
|
|
// History: 2-07-95 srikants Created
|
|
//
|
|
// Notes: There are four kinds of variant data in the rows:
|
|
//
|
|
// 1. In place data (eg. I1, I2, I4, I8, etc )
|
|
// 2. Pointer to global compression data.
|
|
// 3. Pointer to window specific compression data.
|
|
// 4. Pointer to window specific non-compressed data.
|
|
//
|
|
// Data fields of types 1 and 2 can be copied directly from the
|
|
// source row but types 3 and 4 have to be extracted from the
|
|
// source row and then copied.
|
|
//
|
|
// An underlying assumption is that rows in the source and
|
|
// target windows have the same layout.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
TBL_OFF CTableWindow::_CopyRow( CTableWindow & srcWindow, TBL_OFF oSrcTableRow )
|
|
{
|
|
|
|
BYTE * pSrcRow = srcWindow._GetRow( oSrcTableRow );
|
|
WORKID wid = srcWindow.RowWorkid( pSrcRow );
|
|
|
|
BYTE * pThisRow = _RowAlloc();
|
|
|
|
Win4Assert( _cbRowSize == srcWindow._cbRowSize &&
|
|
_Columns.Count() == srcWindow._Columns.Count());
|
|
|
|
//
|
|
// First copy the entire row and then fix up the variable length
|
|
// variant parts.
|
|
//
|
|
RtlCopyMemory( pThisRow, pSrcRow, _cbRowSize );
|
|
TBL_OFF oTableRow = _DataAllocator.FixedOffset(pThisRow);
|
|
|
|
//
|
|
// For each column, determine its type. If it is an in-place value or a
|
|
// globally compressed value, just ignore it as we have already copied the
|
|
// data. O/W, extract the variant and copy its contents.
|
|
//
|
|
for ( unsigned i=0; i < _Columns.Count(); i++)
|
|
{
|
|
CTableColumn * pDstColumn = _Columns.Get(i);
|
|
CTableColumn * pSrcColumn = srcWindow._Columns.Get(i);
|
|
Win4Assert( 0 != pDstColumn && 0 != pSrcColumn );
|
|
|
|
if ( pSrcColumn->IsPartialDeferred() )
|
|
{
|
|
// No Data to copy here
|
|
continue;
|
|
}
|
|
|
|
Win4Assert(
|
|
pDstColumn->GetStoredType() == pSrcColumn->GetStoredType() &&
|
|
pDstColumn->GetValueOffset() == pSrcColumn->GetValueOffset() &&
|
|
pDstColumn->GetStatusOffset() == pSrcColumn->GetStatusOffset() &&
|
|
pDstColumn->GetLengthOffset() == pSrcColumn->GetLengthOffset()
|
|
);
|
|
|
|
VARTYPE vtSrc = pSrcColumn->GetStoredType();
|
|
Win4Assert( pDstColumn->GetStoredType() == vtSrc );
|
|
|
|
USHORT cbData, cbAlignment, rgfFlags;
|
|
CTableVariant::VartypeInfo(vtSrc, cbData, cbAlignment, rgfFlags);
|
|
|
|
if ( CTableVariant::TableIsStoredInline( rgfFlags, vtSrc ) ||
|
|
pSrcColumn->IsGlobalCompressedCol() ||
|
|
! pSrcColumn->IsValueStored())
|
|
{
|
|
//
|
|
// This data is either stored in-line or is a globally compressed
|
|
// data. We can skip and go the next field.
|
|
//
|
|
continue;
|
|
}
|
|
|
|
DBSTATUS CopyStatus = pSrcColumn->CopyColumnData(
|
|
pThisRow,
|
|
*pDstColumn,
|
|
_DataAllocator,
|
|
pSrcRow,
|
|
srcWindow._DataAllocator);
|
|
|
|
Win4Assert(DBSTATUS_S_OK == CopyStatus ||
|
|
DBSTATUS_S_ISNULL == CopyStatus);
|
|
|
|
} // for loop
|
|
|
|
return oTableRow;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: _PutRowToVisibleRowIndex
|
|
//
|
|
// Synopsis: Copies the given row to the table and adds an entry to the
|
|
// visible row index.
|
|
//
|
|
// Arguments: [srcWindow] - Reference to the source window.
|
|
// [oSrcRow] - Row Offset in the source window to be copied.
|
|
//
|
|
// History: 1-24-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_PutRowToVisibleRowIndex( CTableWindow & srcWindow,
|
|
TBL_OFF oSrcRow )
|
|
{
|
|
|
|
BYTE * pbRow = srcWindow._GetRow( oSrcRow );
|
|
|
|
//
|
|
// Add the rowdata and make an entry in the visible row index
|
|
// for this.
|
|
//
|
|
WORKID wid = RowWorkid( pbRow );
|
|
Win4Assert( !_BookMarkMap.IsBookMarkPresent(wid) );
|
|
|
|
TBL_OFF oTableRow = _CopyRow( srcWindow, oSrcRow );
|
|
|
|
_visibleRowIndex.AddRow(oTableRow);
|
|
_BookMarkMap.AddBookMark( wid, oTableRow );
|
|
|
|
const ULONG status = _RowStatus(pbRow);
|
|
|
|
Win4Assert( TBL_DATA_HARD_DELETE != status );
|
|
|
|
Win4Assert( TBL_DATA_OKAY == status ||
|
|
TBL_DATA_PENDING_DELETE == status ||
|
|
TBL_DATA_ROWADDED == status );
|
|
|
|
if ( TBL_DATA_PENDING_DELETE == status )
|
|
{
|
|
_cPendingDeletes++;
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: _PutRowToDynRowIndex
|
|
//
|
|
// Synopsis: Adds the given row to the dynamic row index. In addition, if
|
|
// the row is a "new" row and is not present in the table
|
|
// already, it will be added to the table also.
|
|
//
|
|
// Arguments: [srcWindow] - Source window
|
|
// [oSrcRow] - Offset of the row in the source window.
|
|
//
|
|
// History: 1-24-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_PutRowToDynRowIndex( CTableWindow & srcWindow,
|
|
TBL_OFF oSrcRow )
|
|
{
|
|
BYTE * pbRow = srcWindow._GetRow( oSrcRow );
|
|
WORKID wid = srcWindow.RowWorkid( pbRow );
|
|
|
|
TBL_OFF oTableRow;
|
|
|
|
const ULONG rowStatus = srcWindow._RowStatus(pbRow);
|
|
|
|
Win4Assert( TBL_DATA_HARD_DELETE != rowStatus );
|
|
|
|
if ( TBL_DATA_OKAY == rowStatus || TBL_DATA_PENDING_DELETE == rowStatus )
|
|
{
|
|
//
|
|
// Since the row status is OKAY or pending delete, it MUST have
|
|
// already been added via the _visibleRowIndex.
|
|
//
|
|
BOOL fFound = _BookMarkMap.FindBookMark( wid, oTableRow );
|
|
Win4Assert( fFound );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This row has not been added already via the visible row
|
|
// index. We should add it to the table.
|
|
//
|
|
Win4Assert( TBL_DATA_ROWADDED == rowStatus );
|
|
oTableRow = _CopyRow( srcWindow, oSrcRow );
|
|
Win4Assert( _xDeltaBookMarkMap.IsNull() ||
|
|
!_xDeltaBookMarkMap->IsBookMarkPresent(wid) );
|
|
_xDeltaBookMarkMap->AddBookMark( wid, oTableRow );
|
|
}
|
|
|
|
_dynRowIndex.AddRow( oTableRow );
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: IsEmptyForQuery
|
|
//
|
|
// Synopsis: Checks if the window is empty from query's perspective.
|
|
//
|
|
// Returns: TRUE if there are no entries in the "invisible" row index.
|
|
// FALSE o/w
|
|
//
|
|
// History: 2-07-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::IsEmptyForQuery()
|
|
{
|
|
return _GetInvisibleRowIndex().RowCount() == 0;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: IsGettingFull
|
|
//
|
|
// Synopsis: Checks if the current window is getting full.
|
|
//
|
|
// Returns: TRUE if it is getting full.
|
|
// FALSE o/w
|
|
//
|
|
// History: 2-07-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::IsGettingFull()
|
|
{
|
|
//
|
|
// PERFFIX - need a better heuristic based on memory usage.
|
|
//
|
|
return _GetInvisibleRowIndex().RowCount() >= CTableSink::cWindowRowLimit;
|
|
}
|
|
|
|
unsigned CTableWindow::PercentFull()
|
|
{
|
|
return (_GetInvisibleRowIndex().RowCount() * 100) / CTableSink::cWindowRowLimit;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetSortKey
|
|
//
|
|
// Synopsis: Fills the requested window row as a "bktRow" by extracting
|
|
// only the "sort columns".
|
|
//
|
|
// Arguments: [iRow] - Index of the requested window row.
|
|
// [pvtOut] - Variants of the sort set.
|
|
// [bktRow] - (Output) will have the row in a "bucket row"
|
|
// form.
|
|
//
|
|
// History: 2-16-95 srikants Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CTableWindow::GetSortKey( ULONG iRow, CTableRowKey & sortKey )
|
|
{
|
|
Win4Assert( 0 != _RowCompare.GetPointer() &&
|
|
iRow < _GetInvisibleRowIndex().RowCount() );
|
|
|
|
TBL_OFF oTableRow = _GetInvisibleRowIndex().GetRow(iRow);
|
|
|
|
Win4Assert( 0 != _pSortSet );
|
|
|
|
BYTE * pRow = _GetRow( oTableRow );
|
|
|
|
for ( unsigned i = 0; i < _pSortSet->Count(); i++ )
|
|
{
|
|
const SSortKey & key = _pSortSet->Get(i);
|
|
PROPID pidColumn = key.pidColumn;
|
|
|
|
BOOL fFoundCol = FALSE;
|
|
CTableColumn * pColumn = _Columns.Find(pidColumn, fFoundCol);
|
|
Win4Assert(fFoundCol == TRUE);
|
|
|
|
//
|
|
// Create a variant out of the data in the column and copy its contents
|
|
// to the output row.
|
|
//
|
|
CTableVariant varnt;
|
|
CTableVariant* pvarnt;
|
|
XCompressFreeVariant xpvarnt;
|
|
|
|
pvarnt = &varnt;
|
|
if ( pColumn->CreateVariant( *pvarnt, pRow, _DataAllocator ) )
|
|
{
|
|
//
|
|
// Variant needs to be freed by the column compressor.
|
|
//
|
|
xpvarnt.Set(pColumn->GetCompressor(), pvarnt);
|
|
}
|
|
|
|
//
|
|
// Initialize the column in the bucket row with the data in the
|
|
// variant just extracted.
|
|
//
|
|
sortKey.Init( i, *pvarnt );
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::FindRegion, private
|
|
//
|
|
// Synopsis: Delete watch region
|
|
//
|
|
// Returns: The number of rows deleted from watch
|
|
//
|
|
// History: 22-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
unsigned CTableWindow::FindRegion (HWATCHREGION hRegion) const
|
|
{
|
|
for (unsigned i = 0; i < _aWindowWatch.Count(); i++)
|
|
{
|
|
if (_aWindowWatch.Get(i)._hRegion == hRegion)
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::AddWatch
|
|
//
|
|
// Synopsis: Add watch region starting at offset
|
|
//
|
|
// Returns: The number of rows added to the watch
|
|
//
|
|
// History: 26-Jul-95 BartoszM Created
|
|
//
|
|
// Notes: If this is the last segment, add all cRows
|
|
// otherwise don't overshoot the end of window
|
|
//--------------------------------------------------------------------------
|
|
|
|
long CTableWindow::AddWatch ( HWATCHREGION hRegion,
|
|
long iStart,
|
|
LONG cRows,
|
|
BOOL isLast)
|
|
{
|
|
Win4Assert( cRows > 0 );
|
|
|
|
long cRowsHere = cRows;
|
|
if (!isLast && cRows > (long)_visibleRowIndex.RowCount() - iStart)
|
|
{
|
|
cRowsHere = (long)_visibleRowIndex.RowCount() - iStart;
|
|
}
|
|
|
|
|
|
Win4Assert( cRowsHere >= 0 );
|
|
|
|
CWindowWatch watch;
|
|
watch._hRegion = hRegion;
|
|
watch._iRowStart = iStart;
|
|
watch._cRowsHere = cRowsHere;
|
|
watch._cRowsLeft = cRows;
|
|
_AddWatchItem (watch);
|
|
return cRowsHere;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::DeleteWatch
|
|
//
|
|
// Synopsis: Delete watch region
|
|
//
|
|
// Returns: The number of rows deleted from watch
|
|
//
|
|
// History: 20-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
long CTableWindow::DeleteWatch (HWATCHREGION hRegion)
|
|
{
|
|
unsigned i = FindRegion(hRegion);
|
|
Win4Assert (i < _aWindowWatch.Count());
|
|
long count = _aWindowWatch.Get(i)._cRowsHere;
|
|
_RemoveWatchItem (i);
|
|
return count;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::ModifyWatch
|
|
//
|
|
// Synopsis: Modify watch region
|
|
//
|
|
// Returns: The number of rows in the modified watch
|
|
//
|
|
// History: 20-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
long CTableWindow::ModifyWatch (HWATCHREGION hRegion, long iStart, long cRows, BOOL isLast)
|
|
{
|
|
Win4Assert( cRows > 0 );
|
|
Win4Assert( 0 == iStart || iStart < (long) _visibleRowIndex.RowCount() );
|
|
|
|
unsigned i = FindRegion(hRegion);
|
|
Win4Assert (i < _aWindowWatch.Count());
|
|
CWindowWatch& watch = _aWindowWatch.Get(i);
|
|
long cRowsHere = cRows;
|
|
if ( !isLast && cRows > (long)_visibleRowIndex.RowCount() - iStart )
|
|
{
|
|
cRowsHere = (long)_visibleRowIndex.RowCount() - iStart;
|
|
}
|
|
|
|
Win4Assert( cRowsHere >= 0 );
|
|
|
|
watch._iRowStart = iStart;
|
|
watch._cRowsHere = cRowsHere;
|
|
watch._cRowsLeft = cRows;
|
|
return cRowsHere;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::ShrinkWatch
|
|
//
|
|
// Synopsis: Shrink watch region so that it starts with the
|
|
// bookmark and extends no farther than cRows
|
|
//
|
|
// Returns: The number of rows left in the region
|
|
//
|
|
// History: 21-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
long CTableWindow::ShrinkWatch (HWATCHREGION hRegion,
|
|
CI_TBL_BMK bookmark,
|
|
LONG cRows)
|
|
{
|
|
Win4Assert( cRows > 0 );
|
|
|
|
// find the bookmark
|
|
TBL_OFF off;
|
|
ULONG uiStart;
|
|
if ( !FindBookMark( bookmark, off, uiStart))
|
|
{
|
|
Win4Assert (!"CTableWindow::ShrinkWatch, bookmark not found" );
|
|
}
|
|
|
|
long iStart = (long)uiStart;
|
|
|
|
unsigned i = FindRegion(hRegion);
|
|
Win4Assert (i < _aWindowWatch.Count());
|
|
|
|
CWindowWatch& watch = _aWindowWatch.Get(i);
|
|
// Assert that bookmark is within the watch region
|
|
Win4Assert (watch._iRowStart <= iStart);
|
|
Win4Assert (watch._iRowStart + watch._cRowsHere > iStart);
|
|
|
|
long cRowsLeftHere = watch._cRowsHere - (iStart - watch._iRowStart);
|
|
long cRowsLeft = watch._cRowsLeft - (iStart - watch._iRowStart);
|
|
|
|
Win4Assert( cRowsLeftHere > 0 );
|
|
Win4Assert( cRowsLeft > 0 );
|
|
|
|
watch._iRowStart = iStart;
|
|
watch._cRowsHere = min (cRowsLeftHere, cRows);
|
|
watch._cRowsLeft = min (cRowsLeft, cRows);
|
|
return watch._cRowsHere;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::ShrinkWatch
|
|
//
|
|
// Synopsis: Shrink watch region so that it extends no farther than cRows
|
|
// The assumption is that the beginning of the region
|
|
// is at the beginning of the window.
|
|
//
|
|
// Returns: The number of rows left in the region
|
|
//
|
|
// History: 21-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
long CTableWindow::ShrinkWatch (HWATCHREGION hRegion, LONG cRows)
|
|
{
|
|
unsigned i = FindRegion(hRegion);
|
|
Win4Assert (i < _aWindowWatch.Count());
|
|
Win4Assert (_aWindowWatch.Get(i)._iRowStart == 0);
|
|
CWindowWatch& watch = _aWindowWatch.Get(i);
|
|
|
|
Win4Assert( cRows > 0 );
|
|
Win4Assert( watch._cRowsHere >= 0 );
|
|
Win4Assert( watch._cRowsLeft > 0 );
|
|
|
|
watch._cRowsHere = min (watch._cRowsHere, cRows);
|
|
watch._cRowsLeft = min (watch._cRowsLeft, cRows);
|
|
return watch._cRowsHere;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::IsWatched
|
|
//
|
|
// Synopsis: Returns TRUE if the window has the watch region hRegion
|
|
// and the bookmark is within this region
|
|
//
|
|
// History: 21-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::IsWatched (HWATCHREGION hRegion, CI_TBL_BMK bookmark)
|
|
{
|
|
unsigned i = FindRegion(hRegion);
|
|
|
|
if (i == _aWindowWatch.Count())
|
|
return FALSE;
|
|
// find the bookmark
|
|
TBL_OFF off;
|
|
ULONG iBmk;
|
|
if ( !FindBookMark( bookmark, off, iBmk))
|
|
{
|
|
return FALSE;
|
|
}
|
|
long iWatchStart = _aWindowWatch.Get(i)._iRowStart;
|
|
long iWatchEnd = iWatchStart + _aWindowWatch.Get(i)._cRowsHere;
|
|
return iWatchStart <= (long)iBmk && (long)iBmk < iWatchEnd;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::HasWatch
|
|
//
|
|
// Synopsis: Returns TRUE if the window has the watch region hRegion
|
|
//
|
|
// History: 21-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::HasWatch (HWATCHREGION hRegion)
|
|
{
|
|
unsigned i = FindRegion(hRegion);
|
|
return i < _aWindowWatch.Count();
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::RowsWatched
|
|
//
|
|
// Synopsis: Returns the number of rows in the watch region hRegion
|
|
//
|
|
// History: 22-Jun-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
long CTableWindow::RowsWatched (HWATCHREGION hRegion)
|
|
{
|
|
unsigned i = FindRegion(hRegion);
|
|
if ( i < _aWindowWatch.Count() )
|
|
return _aWindowWatch.Get(i)._cRowsHere;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_AddWatchItem
|
|
//
|
|
// Synopsis: Adds a new watch item to list and
|
|
// changes the state of the window if necessary
|
|
//
|
|
// History: 26-Jul-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_AddWatchItem (CWindowWatch& watch)
|
|
{
|
|
_aWindowWatch.Add (watch, _aWindowWatch.Count());
|
|
if ( _aWindowWatch.Count() == 1 && !_fSplitInProgress )
|
|
{
|
|
_StartStaticDynamicSplit();
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::_RemoveWatcheItem
|
|
//
|
|
// Synopsis: Removes a watch item from the list and
|
|
// changes the state of the window if necessary
|
|
//
|
|
// History: 26-Jul-95 BartoszM Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CTableWindow::_RemoveWatchItem (unsigned i)
|
|
{
|
|
_aWindowWatch.Remove(i);
|
|
if (_aWindowWatch.Count() == 0)
|
|
{
|
|
_EndStaticDynamicSplit();
|
|
}
|
|
}
|
|
|
|
BOOL CTableWindow::FindNearestDynamicBmk( 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
|
|
|
|
//
|
|
// NEWFEATURE - does not understand categorization .
|
|
//
|
|
Win4Assert( IsWatched() );
|
|
|
|
if ( WORKID_TBLFIRST == bookMark || WORKID_TBLLAST == bookMark )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
TBL_OFF iRowOffset;
|
|
|
|
if ( _xDeltaBookMarkMap->FindBookMark(bookMark, iRowOffset) )
|
|
{
|
|
//
|
|
// The row is present in the delta bookmark map. Just return
|
|
// it.
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Locate the closest row in the dynamic bookmark mapping
|
|
// for the row.
|
|
//
|
|
BOOL fFound = _BookMarkMap.FindBookMark(bookMark, iRowOffset);
|
|
Win4Assert( fFound );
|
|
|
|
//
|
|
// Locate the row offset in the dynamic row index and find the
|
|
// closest row which is not in a pending delete state.
|
|
//
|
|
ULONG iRowOrdinal;
|
|
fFound = _dynRowIndex.FindRow(iRowOffset,iRowOrdinal);
|
|
Win4Assert( fFound );
|
|
|
|
for ( ULONG i = iRowOrdinal; i < _dynRowIndex.RowCount(); i++ )
|
|
{
|
|
BYTE * pbRow = (BYTE *) _DataAllocator.FixedPointer(
|
|
_dynRowIndex.GetRow(i) );
|
|
|
|
ULONG status = _RowStatus(pbRow);
|
|
if ( TBL_DATA_PENDING_DELETE != status )
|
|
{
|
|
Win4Assert( TBL_DATA_HARD_DELETE != status );
|
|
bookMark = RowWorkid(pbRow);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Try to the left of the bookmark.
|
|
//
|
|
for ( long j = (long) iRowOrdinal-1; j >=0; j-- )
|
|
{
|
|
BYTE * pbRow = (BYTE *) _DataAllocator.FixedPointer(
|
|
_dynRowIndex.GetRow( (ULONG) j) );
|
|
|
|
ULONG status = _RowStatus(pbRow);
|
|
if ( TBL_DATA_PENDING_DELETE != status )
|
|
{
|
|
Win4Assert( TBL_DATA_HARD_DELETE != status );
|
|
bookMark = RowWorkid(pbRow);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CTableWindow::FindFirstNonDeleteDynamicBmk( CI_TBL_BMK & bmk )
|
|
{
|
|
|
|
CRowIndex & rowIndex = _GetInvisibleRowIndex();
|
|
|
|
for ( unsigned i = 0; i < rowIndex.RowCount(); i++ )
|
|
{
|
|
BYTE * pbRow = (BYTE *)
|
|
_DataAllocator.FixedPointer( rowIndex.GetRow(i) );
|
|
if ( TBL_DATA_PENDING_DELETE != _RowStatus(pbRow) )
|
|
{
|
|
Win4Assert( TBL_DATA_HARD_DELETE != _RowStatus(pbRow) );
|
|
bmk = RowWorkid(pbRow);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CTableWindow::FindLastNonDeleteDynamicBmk( CI_TBL_BMK & bmk )
|
|
{
|
|
|
|
CRowIndex & rowIndex = _GetInvisibleRowIndex();
|
|
|
|
for ( long i = (long) rowIndex.RowCount()-1; i >= 0; i-- )
|
|
{
|
|
BYTE * pbRow = (BYTE *)
|
|
_DataAllocator.FixedPointer( rowIndex.GetRow( (ULONG) i) );
|
|
if ( TBL_DATA_PENDING_DELETE != _RowStatus(pbRow) )
|
|
{
|
|
Win4Assert( TBL_DATA_HARD_DELETE != _RowStatus(pbRow) );
|
|
bmk = RowWorkid(pbRow);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::LokGetOneColumn
|
|
//
|
|
// Synopsis: Fills a buffer with a particular column from a row
|
|
//
|
|
// Arguments: [wid] -- workid of the row to be queried
|
|
// [rOutColumn] -- layout of the output data
|
|
// [pbOut] -- where to write the column data
|
|
// [rVarAlloc] -- variable data allocator to use
|
|
//
|
|
// History: 22-Aug-95 dlee Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CTableWindow::LokGetOneColumn(
|
|
WORKID wid,
|
|
CTableColumn const & rOutColumn,
|
|
BYTE * pbOut,
|
|
PVarAllocator & rVarAllocator )
|
|
{
|
|
TBL_OFF oRow;
|
|
ULONG iRow;
|
|
BOOL fFound = FindBookMark( wid, oRow, iRow );
|
|
|
|
Win4Assert( fFound && "LokGetOneColumn could not find bmk" );
|
|
|
|
BYTE *pbRow = (BYTE *) _DataAllocator.FixedPointer( oRow );
|
|
|
|
CTableColumn * pColumn = _Columns.Find( rOutColumn.PropId );
|
|
|
|
pColumn->CopyColumnData( pbOut,
|
|
rOutColumn,
|
|
rVarAllocator,
|
|
pbRow,
|
|
_DataAllocator );
|
|
} //LokGetOneColumn
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CTableWindow::IsFirstRowFirstOfCategory
|
|
//
|
|
// Synopsis: Returns TRUE if the first row in the window is also the
|
|
// first of a category. Needed for window selection in PutRow
|
|
//
|
|
// History: 6-Nov-95 dlee Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CTableWindow::IsFirstRowFirstOfCategory()
|
|
{
|
|
CRowIndex & ri = _GetInvisibleRowIndex();
|
|
|
|
if ( 0 != ri.RowCount() )
|
|
{
|
|
BYTE *pbRow = _GetRowFromIndex( 0, ri );
|
|
ULONG chapt = _RowChapter( pbRow );
|
|
WORKID wid = RowWorkid( pbRow );
|
|
|
|
CCategorize & Cat = * GetCategorizer();
|
|
|
|
return ( Cat.GetFirstWorkid( chapt ) == wid );
|
|
}
|
|
|
|
return FALSE;
|
|
} //IsFirstRowFirstOfCategory
|
|
|
|
|
|
|