//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1994 - 2000. // // File: query.cxx // // Contents: Class encapsulating all the context for a running // query, including the query execution context, the // cached query results, and all cursors over the // results. // Dispatches requests to the appropriate subobject. // // Classes: CAsyncQuery // CGetRowsParams // // History: 31 May 94 AlanW Created // //-------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include #include "tabledbg.hxx" //+------------------------------------------------------------------------- // // Member: CGetRowsParams::GetRowBuffer, public inline // // Synopsis: Return a pointer to the row buffer. If this is the first // reference, get it from the fixed allocator. // // Arguments: - none - // // Returns: PBYTE - a pointer to the row buffer. // // Notes: We don't put this in query.hxx since we don't want to // have to refer to PFixedVarAllocator there. // //-------------------------------------------------------------------------- PBYTE CGetRowsParams::GetRowBuffer( void ) { if (0 == _pData) _pData = _rAllocator.AllocFixed(); return (PBYTE) _pData; } PBYTE CGetRowsParams::GetBuffer( ) const { return _rAllocator.BufferAddr(); } //+--------------------------------------------------------------------------- // // Member: CAsyncQuery::CAsyncQuery, public // // Synopsis: Creates a locally accessible Query // // Arguments: [qopt] - Query optimizer // [col] - Initial set of columns to return // [sort] - Initial sort // [categ] - Categorization specification // [cCursors] - # of cursors to create // [aCursors] - array of cursors returned // [pidremap] - prop ID mapping // [fEnableNotification] - if TRUE, allow watches // [pDocStore] - client doc store // [pEvtComplete] - completion event // //---------------------------------------------------------------------------- CAsyncQuery::CAsyncQuery( XQueryOptimizer & qopt, XColumnSet & col, XSortSet & sort, XCategorizationSet &categ, unsigned cCursors, ULONG * aCursors, XInterface & pidremap, BOOL fEnableNotification, ICiCDocStore *pDocStore, CRequestServer * pQuiesce ) : _fCanDoWorkidToPath( !qopt->IsWorkidUnique() ), PQuery( ), _ref( 1 ), _pidremap( pidremap.Acquire() ), _Table( col, sort, cCursors - 1, _mutex, !_fCanDoWorkidToPath, pQuiesce ), _cRowsLastAsked (0), _aCursors( ), _aCategorize( cCursors - 1 ) { // // Get ci manager and translator interfaces // ICiManager *pCiManager = 0; SCODE sc = pDocStore->GetContentIndex( &pCiManager ); if ( FAILED( sc ) ) { Win4Assert( !"Need to support GetContentIndex interface" ); THROW( CException( sc ) ); } _xCiManager.Set( pCiManager ); ICiCDocNameToWorkidTranslator *pNameToWidTranslator; sc = pDocStore->QueryInterface( IID_ICiCDocNameToWorkidTranslator, (void **) &pNameToWidTranslator ); if ( FAILED( sc ) ) { Win4Assert( !"Need to support translator QI" ); THROW( CException( sc ) ); } _xNameToWidTranslator.Set( pNameToWidTranslator ); Win4Assert( 0 != cCursors ); // make a cursor for the main table and each categorization level for (unsigned cursor = 0; cursor < cCursors; cursor++) aCursors[cursor] = _CreateRowCursor(); // the last cursor is associated with the main table _aCursors.Lookup(aCursors[cCursors - 1]).SetSource( &_Table ); if (cCursors > 1) { // // NOTE: pidWorkid is always added to the sort set in the table, // so it should always be sorted. That's why it's sort count-1 // SPECDEVIATION - if workid was already in the sort, this test // is incorrect. Who would want to categorize on workid though? // if ( ! _Table.IsSorted() || cCursors-1 > _Table.GetSortSet()->Count()-1 ) { // CLEANCODE: Should this check be handled in the categorizers? // Is it specific to the unique categorization? tbDebugOut(( DEB_WARN, "Query contains too few sort specs " "for categorization spec\n" )); THROW( CException( E_INVALIDARG ) ); } // this is a categorized table, so set up the categorizers for ( unsigned cat = 0; cat < (cCursors - 1); cat++ ) { _aCategorize[cat] = new CCategorize( * (categ->Get(cat)), cat + 1, cat ? _aCategorize[cat-1] : 0, _mutex ); _aCursors.Lookup(aCursors[cat]).SetSource( _aCategorize[cat] ); } _Table.SetCategorizer(_aCategorize[cCursors - 2]); // now tell each categorizer what it is categorizing over for ( cat = 0; cat < (cCursors - 2); cat++ ) _aCategorize[cat]->SetChild( _aCategorize[ cat + 1 ] ); _aCategorize[ cCursors - 2 ]->SetChild( & _Table ); } // Without the lock, the query can be complete and destructed // BEFORE the constructor finishes CLock lock( _mutex ); // // Since we can be swapping rows, we may need singleton cursor(s) // qopt->EnableSingletonCursors(); if ( 0 != pQuiesce ) pQuiesce->SetPQuery( (PQuery *) this ); _QExec.Set( new CQAsyncExecute( qopt, fEnableNotification, _Table, pDocStore ) ); _Table.SetQueryExecute( _QExec.GetPointer() ); ciDebugOut(( DEB_USER1, "Using an asynchronous cursor.\n" )); END_CONSTRUCTION( CAsyncQuery ); } //+--------------------------------------------------------------------------- // // Member: CAsyncQuery::~CAsyncQuery, public // // Synopsis: Destroy the query // //---------------------------------------------------------------------------- CAsyncQuery::~CAsyncQuery() { CLock lock( _mutex ); //Win4Assert( _ref == 0 ); This isn't true when queries are aborted _Table.ReleaseQueryExecute(); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::AddRef, public // // Synopsis: Reference the query. // //-------------------------------------------------------------------------- ULONG CAsyncQuery::AddRef(void) { return InterlockedIncrement( &_ref ); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::Release, public // // Synopsis: De-Reference the query. // // Effects: If the ref count goes to 0 then the query is deleted. // //-------------------------------------------------------------------------- ULONG CAsyncQuery::Release(void) { long l = InterlockedDecrement( &_ref ); if ( l <= 0 ) { tbDebugOut(( DEB_ITRACE, "CAsyncQuery unreferenced. Deleting.\n" )); delete this; return 0; } return l; } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::_CreateRowCursor, private // // Synopsis: Create a new CTableCursor, return a handle to it. // // Arguments: - none - // // Returns: ULONG - handle associated with the new cursor. Will // be zero if the cursor could not be created. // // Notes: There needs to be a subsequent call to SetBindings prior // to any call to GetRows. // //-------------------------------------------------------------------------- ULONG CAsyncQuery::_CreateRowCursor( ) { CTableCursor * pCursor = new CTableCursor; ULONG hCursor = 0; _aCursors.Add(pCursor, hCursor); // By default, make the cursor refer to the real table, // not categorization pCursor->SetSource(&_Table); Win4Assert(hCursor != 0); return hCursor; } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::FreeCursor, public // // Synopsis: Free a handle to a CTableCursor // // Arguments: [hCursor] - handle to the cursor to be freed // // Returns: # of cursors left // //-------------------------------------------------------------------------- unsigned CAsyncQuery::FreeCursor( ULONG hCursor ) { Win4Assert( hCursor != 0 ); _aCursors.Release(hCursor); return _aCursors.Count(); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::SetBindings, public // // Synopsis: Set column bindings into a cursor // // Arguments: [hCursor] - the handle of the cursor to set bindings on // [cbRowLength] - the width of an output row // [cols] - a description of column bindings to be set // [pids] - a PID mapper which maps fake pids in cols to // column IDs. // // Returns: nothing // //-------------------------------------------------------------------------- void CAsyncQuery::SetBindings( ULONG hCursor, ULONG cbRowLength, CTableColumnSet & cols, CPidMapper & pids ) { CTableCursor& rCursor = _aCursors.Lookup(hCursor); if (0 == cols.Count() || 0 == cbRowLength || cbRowLength >= USHRT_MAX) THROW( CException( E_INVALIDARG )); XPtr outset(new CTableColumnSet( cols.Count() )); for (unsigned iCol = 0; iCol < cols.Count(); iCol++) { CTableColumn * pCol = cols.Get( iCol ); CFullPropSpec * propspec = pids.Get( pCol->PropId ); // // Convert the DBID to a PROPID // // Win4Assert( iCol+1 == pCol->PropId ); // therefore pids is useless PROPID prop = _pidremap->NameToReal(propspec); if ( prop == pidInvalid || _Table.IsColumnInTable(prop) == FALSE ) { tbDebugOut(( DEB_ERROR, "Column unavailable: prop = 0x%x\n", prop )); THROW( CException( DB_E_BADCOLUMNID )); } if (pCol->IsCompressedCol()) THROW( CException( E_INVALIDARG )); XPtr xpOutcol ( new CTableColumn( prop, pCol->GetStoredType() ) ); if (pCol->IsValueStored()) xpOutcol->SetValueField(pCol->GetStoredType(), pCol->GetValueOffset(), pCol->GetValueSize()); Win4Assert( pCol->IsStatusStored() ); xpOutcol->SetStatusField(pCol->GetStatusOffset(), (USHORT)pCol->GetStatusSize()); if (pCol->IsLengthStored()) xpOutcol->SetLengthField(pCol->GetLengthOffset(), (USHORT)pCol->GetLengthSize()); outset->Add(xpOutcol, iCol); } SCODE sc = rCursor.SetBindings( cbRowLength, outset ); if ( FAILED( sc ) ) THROW( CException( sc ) ); } //SetBindings //+------------------------------------------------------------------------- // // Member: CAsyncQuery::GetRows, public // // Synopsis: Retrieve row data for a table cursor // // Arguments: [hCursor] - the handle of the cursor to fetch data for // [rSeekDesc] - row seek operation to be done before fetch // [rFetchParams] - row fetch parameters and buffer pointers // [pSeekDescOut] - row seek description for restart // // Returns: SCODE - the status of the operation. E_HANDLE // is returned if hCursor cannot be looked up, // presumably an internal error. // // History: 24 Jan 1995 Alanw Created // //-------------------------------------------------------------------------- SCODE CAsyncQuery::GetRows( ULONG hCursor, const CRowSeekDescription & rSeekDesc, CGetRowsParams& rFetchParams, XPtr & pSeekDescOut) { CTableCursor & rCursor = _aCursors.Lookup(hCursor); rCursor.ValidateBindings(); CTableSource & rSource = rCursor.GetSource(); Win4Assert(rCursor.GetRowWidth() == rFetchParams.GetRowWidth()); return rSeekDesc.GetRows( rCursor, rSource, rFetchParams, pSeekDescOut ); } //GetRows //+------------------------------------------------------------------------- // // Member: CAsyncQuery::RestartPosition, public // // Synopsis: Reset fetch position for chapter to the start // // Arguments: [hCursor] - the handle of the cursor to restart // [chapter] - the chapter to restart // // Returns: SCODE - the status of the operation. // // Notes: // // History: 17 Apr 1997 EmilyB Created // //-------------------------------------------------------------------------- void CAsyncQuery::RestartPosition( ULONG hCursor, CI_TBL_CHAPT chapter) { CTableCursor & rCursor = _aCursors.Lookup(hCursor); CTableSource & rSource = rCursor.GetSource(); rSource.RestartPosition ( chapter ); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::RatioFinished, public // // Synopsis: Return the completion status as a fraction // // Arguments: [hCursor] - the handle of the cursor to check completion for // [rulDenominator] - on return, denominator of fraction // [rulNumerator] - on return, numerator of fraction // [rcRows] - on return, number of rows in cursor // [rfNewRows] - on return, TRUE if new rows available // // Returns: nothing // // Notes: A handle of zero can be passed for use with sequential // cursors to check completion before a handle exists. // //-------------------------------------------------------------------------- void CAsyncQuery::RatioFinished( ULONG hCursor, DBCOUNTITEM & rulDenominator, DBCOUNTITEM & rulNumerator, DBCOUNTITEM & rcRows, BOOL & rfNewRows ) { if (hCursor != 0) CTableCursor& rCursor = _aCursors.Lookup(hCursor); // For error check rulDenominator = 1; rulNumerator = 0; rfNewRows = FALSE; unsigned status = QUERY_FILL_STATUS(_Table.Status()); // // SPECDEVIATION: should do something more meaningful with STAT_ERROR // RatioFinished should probably fail in this case. // if (STAT_DONE == status || STAT_ERROR == status || STAT_REFRESH == status) { rulNumerator = 1; rcRows = _Table.RowCount(); if (rcRows != _cRowsLastAsked) { _cRowsLastAsked = rcRows; rfNewRows = TRUE; } return; } _Table.RatioFinished (rulDenominator, rulNumerator, rcRows); if (rcRows != _cRowsLastAsked) { _cRowsLastAsked = rcRows; rfNewRows = TRUE; } Win4Assert( rulDenominator >= rulNumerator ); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::Compare, public // // Synopsis: Return the approximate current position as a fraction // // Arguments: [hCursor] - the handle of the cursor to compare bmks from // [bmkFirst] - First bookmark to compare // [bmkSecond] - Second bookmark to compare // [rdwComparison] - on return, comparison value // // Returns: nothing // //-------------------------------------------------------------------------- void CAsyncQuery::Compare( ULONG hCursor, CI_TBL_CHAPT chapt, CI_TBL_BMK bmkFirst, CI_TBL_BMK bmkSecond, DWORD & rdwComparison ) { rdwComparison = DBCOMPARE_NOTCOMPARABLE; CTableCursor& rCursor = _aCursors.Lookup(hCursor); CTableSource & rSource = rCursor.GetSource(); DBCOUNTITEM ulNum1, ulDen1; SCODE scRet = rSource.GetApproximatePosition( chapt, bmkFirst, &ulNum1, &ulDen1); DBCOUNTITEM ulNum2, ulDen2; if (SUCCEEDED(scRet)) { scRet = rSource.GetApproximatePosition( chapt, bmkSecond, &ulNum2, &ulDen2); } if (SUCCEEDED(scRet)) { Win4Assert(ulDen1 == ulDen2); if (ulNum1 < ulNum2) rdwComparison = DBCOMPARE_LT; else if (ulNum1 > ulNum2) rdwComparison = DBCOMPARE_GT; else // ulNum1 == ulNum2 rdwComparison = DBCOMPARE_EQ; } // CLEANCODE - have the source table THROW if errors in GetApproximatePosition if (scRet != S_OK) THROW(CException(scRet)); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::GetApproximatePosition, public // // Synopsis: Return the approximate current position as a fraction // // Arguments: [hCursor] - the handle of the cursor to retrieve info. from // [bmk] - bookmark of row to get position of // [pulNumerator] - on return, numerator of fraction // [pulDenominator] - on return, denominator of fraction // // Returns: nothing // //-------------------------------------------------------------------------- void CAsyncQuery::GetApproximatePosition( ULONG hCursor, CI_TBL_CHAPT chapt, CI_TBL_BMK bmk, DBCOUNTITEM * pulNumerator, DBCOUNTITEM * pulDenominator ) { CTableCursor& rCursor = _aCursors.Lookup(hCursor); CTableSource & rSource = rCursor.GetSource(); SCODE scRet = rSource.GetApproximatePosition( chapt, bmk, pulNumerator, pulDenominator); // CLEANCODE - have the source table THROW if errors in GetApproximatePosition if (scRet != S_OK) THROW(CException(scRet)); } //+--------------------------------------------------------------------------- // // Member: CAsyncQuery::GetNotifications, private // // Synopsis: Retrieves the notification info from the query object // row data. // // Arguments: [rSync] -- notification synchronization info // [rParams] -- notification data info // // Returns: SCODE // // History: 10-24-94 dlee created // //---------------------------------------------------------------------------- SCODE CAsyncQuery::GetNotifications( CNotificationSync & rSync, DBWATCHNOTIFY & changeType ) { return _Table.GetNotifications(rSync,changeType); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::SetWatchMode // // Synopsis: Stub implementation // // Arguments: [phRegion] -- handle to watch region // [mode] -- watch mode // // History: May-2-95 BartoszM Created // //-------------------------------------------------------------------------- void CAsyncQuery::SetWatchMode ( HWATCHREGION* phRegion, ULONG mode) { if (*phRegion == watchRegionInvalid) _Table.CreateWatchRegion (mode, phRegion); else _Table.ChangeWatchMode (*phRegion, mode); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::GetWatchInfo // // Synopsis: Stub implementation // // Arguments: [hRegion] -- handle to watch region // [pMode] -- watch mode // [pChapter] -- chapter // [pBookmark] -- bookmark // [pcRows] -- number of rows // // History: May-2-95 BartoszM Created // //-------------------------------------------------------------------------- void CAsyncQuery::GetWatchInfo ( HWATCHREGION hRegion, ULONG* pMode, CI_TBL_CHAPT* pChapter, CI_TBL_BMK* pBookmark, DBCOUNTITEM* pcRows) { _Table.GetWatchRegionInfo (hRegion, pChapter, pBookmark, (DBROWCOUNT *)pcRows); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::ShrinkWatchRegion // // Synopsis: Stub implementation // // Arguments: [hRegion] -- handle to watch region // [pChapter] -- chapter // [pBookmark] -- bookmark // [cRows] -- number of rows // // History: May-2-95 BartoszM Created // //-------------------------------------------------------------------------- void CAsyncQuery::ShrinkWatchRegion ( HWATCHREGION hRegion, CI_TBL_CHAPT chapter, CI_TBL_BMK bookmark, LONG cRows ) { if (cRows == 0) _Table.DeleteWatchRegion (hRegion); else _Table.ShrinkWatchRegion (hRegion, chapter, bookmark, cRows); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::Refresh // // Synopsis: Stub implementation // // Arguments: [] -- // // History: Arp-4-95 BartoszM Created // //-------------------------------------------------------------------------- void CAsyncQuery::Refresh() { _Table.Refresh(); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::GetQueryStatus, public // // Synopsis: Return the query status // // Arguments: [hCursor] - the handle of the cursor to check completion for // [rdwStatus] - on return, the query status // // Returns: nothing // //-------------------------------------------------------------------------- void CAsyncQuery::GetQueryStatus( ULONG hCursor, DWORD & rdwStatus) { rdwStatus = _Table.Status(); } //+------------------------------------------------------------------------- // // Member: CAsyncQuery::GetQueryStatusEx, public // // Synopsis: Return the query status plus bonus information. It's kind // of an odd assortment of info, but it saves net trips. // // Arguments: [hCursor] - handle of the cursor to check completion for // [rdwStatus] - returns the query status // [rcFilteredDocuments] - returns # of filtered docs // [rcDocumentsToFilter] - returns # of docs to filter // [rdwRatioFinishedDenominator] - ratio finished denom // [rdwRatioFinishedNumerator] - ratio finished num // [bmk] - bmk to find // [riRowBmk] - index of bmk row // [rcRowsTotal] - # of rows in table // // History: Nov-9-96 dlee Created // //-------------------------------------------------------------------------- void CAsyncQuery::GetQueryStatusEx( ULONG hCursor, DWORD & rdwStatus, DWORD & rcFilteredDocuments, DWORD & rcDocumentsToFilter, DBCOUNTITEM & rdwRatioFinishedDenominator, DBCOUNTITEM & rdwRatioFinishedNumerator, CI_TBL_BMK bmk, DBCOUNTITEM & riRowBmk, DBCOUNTITEM & rcRowsTotal ) { rdwStatus = _Table.Status(); CIF_STATE state; state.cbStruct = sizeof state; SCODE sc = _xCiManager->GetStatus( &state ); if ( SUCCEEDED( sc ) ) { rcFilteredDocuments = state.cFilteredDocuments; rcDocumentsToFilter = state.cDocuments; } else { ciDebugOut(( DEB_ERROR, "CAsyncQuery::GetQueryStatusEx, get status failed, 0x%x\n", sc )); rcFilteredDocuments = 0; rcDocumentsToFilter = 0; } DBCOUNTITEM cRows; BOOL fNewRows; RatioFinished( hCursor, rdwRatioFinishedDenominator, rdwRatioFinishedNumerator, cRows, fNewRows ); GetApproximatePosition( hCursor, 0, bmk, & riRowBmk, & rcRowsTotal ); } //GetQueryStatusEx //+------------------------------------------------------------------------- // // Member: CAsyncQuery::WorkIdToPath // // Synopsis: Converts a wid to a path // // Arguments: [wid] -- of the file to be translated // [funnyPath] -- resulting path // // History: Jun-1-95 dlee Created // //-------------------------------------------------------------------------- void CAsyncQuery::WorkIdToPath( WORKID wid, CFunnyPath & funnyPath ) { if ( _fCanDoWorkidToPath ) { ULONG cbBuf = MAX_PATH * sizeof WCHAR; // first guess -- it may be more XArray xBuf( cbBuf ); CInlineVariant * pVariant = (CInlineVariant *) xBuf.GetPointer(); if (! _Table.WorkIdToPath( wid, *pVariant, cbBuf ) ) { if ( 0 != cbBuf ) { BYTE *pb = xBuf.Acquire(); delete [] pb; cbBuf += sizeof CInlineVariant; xBuf.Init( cbBuf ); _Table.WorkIdToPath( wid, *pVariant, cbBuf ); } } if ( 0 != cbBuf ) { WCHAR *pwc = (WCHAR *) pVariant->GetVarBuffer(); funnyPath.SetPath( pwc ); } } else { ICiCDocName *pDocName; SCODE sc = _xNameToWidTranslator->QueryDocName( &pDocName ); if ( SUCCEEDED( sc ) ) { XInterface xDocName( pDocName ); sc = _xNameToWidTranslator->WorkIdToDocName( wid, xDocName.GetPointer() ); if ( SUCCEEDED( sc ) && sc != CI_S_WORKID_DELETED ) { // PERFFIX: Here we are using two buffers XGrowable and CFunnyPath. // This can be avoided if xDocName->Get can take in CFunnyPath instead of WCHAR* XGrowable xBuf(MAX_PATH); ULONG cb = xBuf.SizeOf(); sc = xDocName->Get( (BYTE *) xBuf.Get(), &cb ); if ( CI_E_BUFFERTOOSMALL == sc ) { xBuf.SetSizeInBytes( cb ); sc = xDocName->Get( (BYTE *) xBuf.Get(), &cb ); } if ( SUCCEEDED( sc ) ) { funnyPath.SetPath( xBuf.Get() ); } } } } } //WorkIdToPath BOOL CAsyncQuery::CanDoWorkIdToPath() { return _fCanDoWorkidToPath; } //CanDoWorkIdToPath //+------------------------------------------------------------------------- // // Member: CAsyncQuery::FetchDeferredValue // // Synopsis: Fetch deferred value from property cache // // Arguments: [wid] -- Workid. // [ps] -- Property to be fetched. // [var] -- Property returned here. // // History: Jun-1-95 KyleP Created // //-------------------------------------------------------------------------- BOOL CAsyncQuery::FetchDeferredValue( WORKID wid, CFullPropSpec const & ps, PROPVARIANT & var ) { // // If using a NULL catalog, assume a file system and go get the value. // The NULL catalog case is only supported by fsci, anyway, so it's // ok to do this hack. // if ( _fCanDoWorkidToPath ) { CFunnyPath funnyPath; WorkIdToPath( wid, funnyPath ); COLEPropManager propMgr; propMgr.Open( funnyPath ); return propMgr.ReadProperty( ps, var ); } return _QExec->FetchDeferredValue( wid, ps, var ); } //FetchDeferredValue