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

857 lines
27 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1995 - 2000.
//
// File: categ.cxx
//
// Contents: Unique categorization class
//
// Classes: CCategorize
//
// History: 30 Mar 95 dlee Created
//
//--------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <query.hxx>
#include "tabledbg.hxx"
//+---------------------------------------------------------------------------
//
// Method: CCategorize, public
//
// Synopsis: Constructor for categorization class
//
// Arguments: [rCatSpec] -- categorization specification for this level
// [iSpec] -- 1-based categorization level, where smaller
// numbers are higher in the hierarchy.
// [pParent] -- categorization object that categorizes rows
// on this level (or 0 if none)
// [mutex] -- CAsyncQuery's mutex for serialization
//
// History: 6-1-95 dlee Created
//
// Notes: Category identifiers start at 0x40000000 plus 0x1000 times
// [iSpec]. The 0x40000000 is so it is obvious to the
// debugger that it is a category. The 0x1000*[iSpec] is
// so that it is obvious what level a category falls into.
//
//----------------------------------------------------------------------------
CCategorize::CCategorize(
CCategorizationSpec & rCatSpec,
unsigned iSpec,
CCategorize * pParent,
CMutexSem & mutex)
: _iSpec( iSpec ),
_pParent( pParent ),
_pChild( 0 ),
_mutex( mutex ),
_iCategoryGen( 0x40000000 + ( 0x1000 * iSpec ) ),
_widCurrent( WORKID_TBLBEFOREFIRST ),
_fNotificationsEnabled( FALSE ),
_iFindHint( 0 ),
_aDynamicCategories( 0 ),
_aVisibleCategories( 16 )
{
if (rCatSpec.Type() != CATEGORIZE_UNIQUE)
THROW(CException( E_INVALIDARG ));
} //CCategorize
//+---------------------------------------------------------------------------
//
// Method: CCategorize::GetRows, public
//
// 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)
// [pOutColumns] - 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.
//
// STATUS_BUFFER_TOO_SMALL 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.
//
// History: 6-1-95 dlee Created
//
//----------------------------------------------------------------------------
SCODE CCategorize::GetRows(
HWATCHREGION hRegion,
WORKID widStart,
CI_TBL_CHAPT chapt,
CTableColumnSet const & rOutColumns,
CGetRowsParams & rGetParams,
WORKID & rwidLastRowTransferred)
{
SCODE sc = S_OK;
TRY
{
if (WORKID_TBLAFTERLAST == widStart)
{
rwidLastRowTransferred = WORKID_TBLAFTERLAST;
return DB_S_ENDOFROWSET;
}
else if (widInvalid == widStart)
{
Win4Assert(! "CCategorize::GetRows widStart is widInvalid");
return E_FAIL;
}
else
{
CLock lock( _mutex );
if ( 0 == _aVisibleCategories.Count() )
sc = DB_S_ENDOFROWSET;
else
{
unsigned iRow;
if ( widStart != WORKID_TBLFIRST &&
widStart != WORKID_TBLBEFOREFIRST )
iRow = _FindCategory( widStart );
else
iRow = 0;
rwidLastRowTransferred = 0;
while ( 0 != rGetParams.RowsToTransfer() &&
iRow < _aVisibleCategories.Count() )
{
// at the end of the chapter when this level is categorized?
if ( ( _isCategorized() ) &&
( DB_NULL_HCHAPTER != chapt ) &&
( _aVisibleCategories[ iRow ].catParent != chapt ) )
break; // code below will set sc = DB_S_ENDOFROWSET
BYTE* pbDst = (BYTE *) rGetParams.GetRowBuffer();
for ( unsigned col = 0; col < rOutColumns.Count(); col++ )
{
CTableColumn const & rDstColumn = *rOutColumns.Get(col);
PROPID pid = rDstColumn.GetPropId();
if ( pidChapter == pid || pidWorkId == pid )
{
if (rDstColumn.GetStoredType() == VT_VARIANT)
{
Win4Assert( rDstColumn.GetValueSize() == sizeof VARIANT );
CTableVariant * pVarnt = (CTableVariant *) ( pbDst +
rDstColumn.GetValueOffset() );
pVarnt->vt = VT_UI4;
pVarnt->ulVal = _aVisibleCategories[iRow].catID;
}
else
{
Win4Assert( rDstColumn.GetValueSize() == sizeof CI_TBL_CHAPT );
RtlCopyMemory( pbDst + rDstColumn.GetValueOffset(),
&( _aVisibleCategories[iRow].catID),
sizeof CI_TBL_CHAPT );
}
rDstColumn.SetStatus( pbDst, CTableColumn::StoreStatusOK );
rDstColumn.SetStatus( pbDst, CTableColumn::StoreStatusOK );
}
else
{
_pChild->LokGetOneColumn( _aVisibleCategories[iRow].widFirst,
rDstColumn,
pbDst,
rGetParams.GetVarAllocator() );
}
}
rwidLastRowTransferred = _aVisibleCategories[iRow].catID;
rGetParams.IncrementRowCount();
if ( rGetParams.GetFwdFetch() )
iRow++;
else
{
if (iRow == 0)
break;
iRow--;
}
}
// If we didn't transfer as many rows as requested, we must
// have run into the end of the table or chapter.
if ( rGetParams.RowsToTransfer() > 0 )
{
if ( 0 == rGetParams.RowsTransferred() )
sc = DB_E_BADSTARTPOSITION;
else
sc = DB_S_ENDOFROWSET;
}
}
}
}
CATCH( CException, e )
{
sc = e.GetErrorCode();
Win4Assert( E_OUTOFMEMORY == sc ||
STATUS_BUFFER_TOO_SMALL == sc ); // benign?
if ( E_OUTOFMEMORY == sc && rGetParams.RowsTransferred() > 0)
sc = DB_S_BLOCKLIMITEDROWS;
}
END_CATCH;
return sc;
} //GetRows
//+-------------------------------------------------------------------------
//
// Member: CCategorize::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 CCategorize::RestartPosition(
CI_TBL_CHAPT chapt)
{
SetCurrentPosition( chapt, WORKID_TBLBEFOREFIRST );
CTableSource::RestartPosition( chapt );
}
//+---------------------------------------------------------------------------
//
// Method: LocateRelativeRow, public
//
// Synopsis: Finds a row in the category table. Since there is only one
// category segment, we are almost assured of finding the row.
//
// Arguments: [widStart] -- where to start the locate
// [chapt] -- parent chapter in which to do the locate
// [cRowsToMove] -- rows to skip after [widStart]
// [rwidRowOut] -- wid found after locate
// [rcRowsResidual] -- number of rows left over -- will be 0
//
// History: 6-1-95 dlee Created
//
//----------------------------------------------------------------------------
SCODE CCategorize::LocateRelativeRow(
WORKID widStart,
CI_TBL_CHAPT chapt,
DBROWOFFSET cRowsToMove,
WORKID & rwidRowOut,
DBROWOFFSET & rcRowsResidual)
{
CLock lock( _mutex );
if ( widStart == WORKID_TBLBEFOREFIRST && cRowsToMove > 0 )
{
widStart = WORKID_TBLFIRST;
cRowsToMove--;
}
else if ( widStart == WORKID_TBLAFTERLAST && cRowsToMove < 0 )
{
widStart = WORKID_TBLLAST;
cRowsToMove++;
}
else if ( WORKID_TBLAFTERLAST == widStart ||
WORKID_TBLBEFOREFIRST == widStart )
{
rwidRowOut = widStart;
rcRowsResidual = cRowsToMove;
return S_OK;
}
ULONG iRow;
if ( WORKID_TBLFIRST == widStart )
{
if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
iRow = _FindCategory( _pParent->GetFirstWorkid( chapt ) );
else
iRow = 0;
}
else if ( WORKID_TBLLAST == widStart )
{
if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
{
iRow = _FindCategory( _pParent->GetFirstWorkid( chapt ) );
iRow += _pParent->GetRowCount( chapt );
}
else
{
iRow = _aVisibleCategories.Count();
}
iRow--;
}
else
{
iRow = _FindCategory( widStart );
}
rcRowsResidual = cRowsToMove + iRow;
if (rcRowsResidual < 0)
{
rwidRowOut = WORKID_TBLBEFOREFIRST;
tbDebugOut(( DEB_ITRACE,
"category table LocateRelativeRow off beginning of table\n" ));
}
else if ( (ULONG) rcRowsResidual >= _aVisibleCategories.Count() )
{
rwidRowOut = WORKID_TBLAFTERLAST;
rcRowsResidual -= _aVisibleCategories.Count();
tbDebugOut(( DEB_ITRACE,
"category table LocateRelativeRow off end of table\n" ));
}
else
{
rwidRowOut = (WORKID) _aVisibleCategories[ (unsigned) rcRowsResidual ].catID;
rcRowsResidual = 0;
}
return S_OK;
} //LocateRelativeRow
//+---------------------------------------------------------------------------
//
// Method: CCategorize::LokAssignCategory, public
//
// Synopsis: Assigns a category to a row in a lower level, then calls
// the parent categorizer to re-categorize the row's category
// if there is a parent.
//
// Arguments: [prm] -- category parameters. See tblsink.hxx for more
// info about this object.
//
// History: 6-1-95 dlee Created
//
// Notes: No need to grab the CAsyncQuery lock -- bigtable grabs it
// up front in PutRow.
//
//----------------------------------------------------------------------------
unsigned CCategorize::LokAssignCategory(
CCategParams & prm)
{
unsigned category = chaptInvalid;
unsigned iEntry = 0xffffffff;
CDynArrayInPlace<CCategory> & array = _WritableArray();
if ( widInvalid != prm.widPrev &&
widInvalid != prm.widNext &&
prm.catPrev == prm.catNext )
{
// new row is between two rows of the same category
// no need to call parent categorizer -- just return
_IncrementRowCount( prm.catPrev );
return prm.catPrev;
}
else if ( widInvalid == prm.widPrev &&
widInvalid == prm.widNext )
{
// first row we've ever seen
Win4Assert( 0 == array.Count() );
CCategory cat( prm.widRow );
array[ 0 ] = cat;
category = _GenCategory();
array[ 0 ].catID = category;
iEntry = 0;
}
else if ( widInvalid == prm.widPrev )
{
// new first row in the next row's category or a new category
if ( prm.icmpNext > _iSpec )
{
// new first row in this (the next row's) category
category = prm.catNext;
iEntry = _FindWritableCategory( category );
array[ iEntry ].widFirst = prm.widRow;
array[ iEntry ].cRows++;
return category;
}
else
{
// insert new category before the next category
iEntry = _FindWritableCategory( prm.catNext );
category = _InsertNewCategory( prm.widRow, iEntry );
}
}
else if ( widInvalid == prm.widNext )
{
// new category OR
// new element in previous row's category
if ( prm.icmpPrev <= _iSpec )
{
// new category after previous (may be an insert operation).
// just because widNext is invalid doesn't mean it doesn't exist.
iEntry = 1 + _FindWritableCategory( prm.catPrev );
category = _InsertNewCategory( prm.widRow, iEntry );
}
else
{
// new element in previous row's category
_IncrementRowCount( prm.catPrev );
return prm.catPrev;
}
}
else
{
// good rows on either side in different categories, one of either:
// new member of previous row's category OR
// new first member of next rows's category OR
// new category
if ( prm.icmpPrev > _iSpec )
{
// new member of previous row's category
_IncrementRowCount( prm.catPrev );
return prm.catPrev;
}
else if ( prm.icmpNext > _iSpec )
{
// new first member of next rows's category
iEntry = _FindWritableCategory( prm.catNext );
array[ iEntry ].widFirst = prm.widRow;
array[ iEntry ].cRows++;
return prm.catNext;
}
else
{
// new category
iEntry = _FindWritableCategory( prm.catNext ) ;
category = _InsertNewCategory( prm.widRow, iEntry );
}
}
// Not all cases above get to this point. Several return early if
// there is no way a parent would care about the operation.
if ( _isCategorized() )
{
Win4Assert( category != chaptInvalid );
Win4Assert( iEntry != 0xffffffff );
// Get the parent category. Use a different CCategParams so original
// is intact.
CCategParams prnt = prm;
prnt.widRow = category;
if ( 0 == iEntry )
prnt.widPrev = widInvalid;
else
{
prnt.widPrev = array[ iEntry - 1 ].catID;
prnt.catPrev = array[ iEntry - 1 ].catParent;
}
if ( iEntry < ( array.Count() - 1 ) )
{
prnt.widNext = array[ iEntry + 1 ].catID;
prnt.catNext = array[ iEntry + 1 ].catParent;
}
else
prnt.widNext = widInvalid;
array[ iEntry ].catParent = _pParent->LokAssignCategory( prnt );
}
return category;
} //LokAssignCategory
//+---------------------------------------------------------------------------
//
// Method: _FindCategory, private
//
// Synopsis: Finds a category in the category array
//
// Arguments: [cat] -- category
//
// Returns: index into the category array
//
// History: 6-1-95 dlee Created
//
//----------------------------------------------------------------------------
unsigned CCategorize::_FindCategory(
CI_TBL_CHAPT cat )
{
unsigned cCategories = _aVisibleCategories.Count();
// first try the hint and the hint + 1
if ( _iFindHint < cCategories )
{
if ( cat == _aVisibleCategories[ _iFindHint ].catID )
return _iFindHint;
unsigned iHintPlus = _iFindHint + 1;
if ( ( iHintPlus < cCategories ) &&
( cat == _aVisibleCategories[ iHintPlus ].catID ) )
{
_iFindHint++;
return _iFindHint;
}
}
// linear search for the category
for ( unsigned i = 0; i < cCategories; i++ )
{
if ( cat == _aVisibleCategories[ i ].catID )
{
_iFindHint = i;
return i;
}
}
THROW( CException( DB_E_BADCHAPTER ) );
return 0;
} //_FindCategory
//+---------------------------------------------------------------------------
//
// Method: _FindWritableCategory, private
//
// Synopsis: Finds a category in the updatable category array
//
// Arguments: [cat] -- category
//
// Returns: index into the category array
//
// History: 6-1-95 dlee Created
//
// PERFPERF: Linear search -- we may want to put a hash table over this.
// How many categories do we expect?
// Another alternative is to cache the last array entry
// referenced and use it and the prev/next entries as first
// guesses.
//
//----------------------------------------------------------------------------
unsigned CCategorize::_FindWritableCategory(
CI_TBL_CHAPT cat )
{
CDynArrayInPlace<CCategory> & array = _WritableArray();
for ( unsigned i = 0; i < array.Count(); i++ )
if ( cat == array[ i ].catID )
return i;
Win4Assert( !"_FindWritableCategory failed" );
THROW( CException( DB_E_BADCHAPTER ) );
return 0;
} //_FindWritableCategory
//+---------------------------------------------------------------------------
//
// Method: GetRowsAt, public
//
// Synopsis: Retrieves rows at a specified location
//
// Arguments: [widStart] -- where to start retrieving rows
// [chapt] -- in which rows are retrieved
// [cRowsToMove] -- offset from widStart
// [rOutColumns] -- description of output columns
// [rGetParams] -- info about the get operation
// [rwidLastRowTransferred] -- last row retrieved
//
// Returns: SCODE
//
// History: 6-1-95 dlee Created
//
//----------------------------------------------------------------------------
SCODE CCategorize::GetRowsAt(
HWATCHREGION hRegion,
WORKID widStart,
CI_TBL_CHAPT chapt,
DBROWOFFSET cRowsToMove,
CTableColumnSet const & rOutColumns,
CGetRowsParams & rGetParams,
WORKID & rwidLastRowTransferred )
{
CLock lock( _mutex );
DBROWOFFSET cRowsResidual;
SCODE scRet = LocateRelativeRow( widStart,
chapt,
cRowsToMove,
widStart,
cRowsResidual);
Win4Assert( !FAILED( scRet ) );
if ( cRowsResidual )
{
Win4Assert ( WORKID_TBLAFTERLAST == widStart ||
WORKID_TBLBEFOREFIRST == widStart );
return DB_E_BADSTARTPOSITION;
}
scRet = GetRows( hRegion,
widStart,
chapt,
rOutColumns,
rGetParams,
rwidLastRowTransferred);
return scRet;
} //GetRowsAt
//+---------------------------------------------------------------------------
//
// Method: GetRowsAtRatio, public
//
// Synopsis: Retrieves rows at a specified location. Nothing fuzzy about
// this -- they're all in memory so be as exact as possible.
//
// Arguments: [num] -- numerator of starting point fraction
// [denom] -- denominator of starting point fraction
// [chapt] -- in which rows are retrieved
// [rOutColumns] -- description of output columns
// [rGetParams] -- info about the get operation
// [rwidLastRowTransferred] -- last row retrieved
//
// Returns: SCODE
//
// History: 6-1-95 dlee Created
//
//----------------------------------------------------------------------------
SCODE CCategorize::GetRowsAtRatio(
HWATCHREGION hRegion,
ULONG num,
ULONG denom,
CI_TBL_CHAPT chapt,
CTableColumnSet const & rOutColumns,
CGetRowsParams & rGetParams,
WORKID & rwidLastRowTransferred )
{
CLock lock( _mutex );
if ( 0 == denom || num > denom )
QUIETTHROW( CException(DB_E_BADRATIO) );
ULONG cRows;
if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
cRows = _pParent->GetRowCount( chapt );
else
cRows = _aVisibleCategories.Count();
ULONG cRowsFromFront = (ULONG) ( ( (unsigned _int64) num *
(unsigned _int64) cRows ) /
( unsigned _int64 ) denom );
if ( cRowsFromFront == cRows )
{
// The user is asking to retrieve past the end of table.
rwidLastRowTransferred = WORKID_TBLAFTERLAST;
return DB_S_ENDOFROWSET;
}
return GetRowsAt( hRegion, WORKID_TBLFIRST, chapt, (LONG) cRowsFromFront,
rOutColumns, rGetParams, rwidLastRowTransferred );
} //GetRowsAtRatio
//+---------------------------------------------------------------------------
//
// Method: RemoveRow, public
//
// Synopsis: Removes a row from the categorization
//
// Arguments: [chapt] -- of row being removed
// [wid] -- of removed row in categorized child table
// [widNext] -- of row following removed row
//
// History: 6-1-95 dlee Created
//
//----------------------------------------------------------------------------
void CCategorize::RemoveRow(
CI_TBL_CHAPT chapt,
WORKID wid,
WORKID widNext )
{
CLock lock( _mutex );
CDynArrayInPlace<CCategory> & array = _WritableArray();
unsigned iEntry = _FindWritableCategory( chapt );
array[ iEntry ].cRows--;
if ( 0 == array[ iEntry ].cRows )
{
// in case we need parent category later, save it
unsigned catParent = array[ iEntry ].catParent;
// last member of category -- remove the category
array.Remove( iEntry );
if ( _isCategorized() )
{
// notify the parent that a row was deleted
WORKID widNextCategory;
if ( ( 0 == array.Count() ) ||
( iEntry >= ( array.Count() - 1) ) )
widNextCategory = widInvalid;
else
widNextCategory = array[ iEntry ].catID;
_pParent->RemoveRow( catParent,
chapt,
widNextCategory );
}
}
else if ( array[ iEntry ].widFirst == wid )
{
// new first member of the category
array[ iEntry ].widFirst = widNext;
// removed the GetNextRows() current position row -- fixup
if ( array[ iEntry ].widGetNextRowsPos == wid )
array[ iEntry ].widGetNextRowsPos = widNext;
}
} //RemoveRow
//+---------------------------------------------------------------------------
//
// Method: GetApproximatePosition, public
//
// Synopsis: Returns the offset of a bookmark in the table/chapter and
// the number of rows in that table/chapter
//
// Arguments: [chapt] -- of row being queried
// [bmk] -- of row being queried
// [piRow] -- returns index of row in table/chapter
// [pcRows] -- returns count of rows in table/chapter
//
// History: 6-29-95 dlee Created
//
//----------------------------------------------------------------------------
SCODE CCategorize::GetApproximatePosition(
CI_TBL_CHAPT chapt,
CI_TBL_BMK bmk,
DBCOUNTITEM * piRow,
DBCOUNTITEM * pcRows )
{
CLock lock( _mutex );
if (bmk == widInvalid)
return DB_E_BADBOOKMARK;
Win4Assert( bmk != WORKID_TBLBEFOREFIRST && bmk != WORKID_TBLAFTERLAST );
DBCOUNTITEM iBmkPos = ULONG_MAX, cRows = 0;
if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
cRows = _pParent->GetRowCount( chapt );
else
cRows = _aVisibleCategories.Count();
if ( WORKID_TBLFIRST == bmk )
iBmkPos = cRows ? 1 : 0;
else if ( WORKID_TBLLAST == bmk )
iBmkPos = cRows;
else
{
iBmkPos = _FindCategory( bmk ) + 1;
// position is relative to first member of the category (if any)
if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
iBmkPos -= _FindCategory( _pParent->GetFirstWorkid( chapt ) );
}
Win4Assert(iBmkPos <= cRows);
*piRow = iBmkPos;
*pcRows = cRows;
return S_OK;
} //GetApproximatePosition
//+---------------------------------------------------------------------------
//
// Method: LokGetOneColumn, public
//
// Synopsis: Returns column data for the first item in a category.
//
// Arguments: [wid] -- workid or chapter 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 CCategorize::LokGetOneColumn(
WORKID wid,
CTableColumn const & rOutColumn,
BYTE * pbOut,
PVarAllocator & rVarAlloc )
{
unsigned iRow = _FindCategory( wid );
_pChild->LokGetOneColumn( _aVisibleCategories[iRow].widFirst,
rOutColumn,
pbOut,
rVarAlloc );
} //LokGetOneColumn