683 lines
22 KiB
C++
683 lines
22 KiB
C++
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 2000.
|
|
//
|
|
// File: FATQuery.cxx
|
|
//
|
|
// Contents: IInternalQuery interface
|
|
//
|
|
// History: 18-Jun-93 KyleP Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#include <pch.cxx>
|
|
#pragma hdrstop
|
|
|
|
#include <fatquery.hxx>
|
|
#include <pidmap.hxx>
|
|
#include <coldesc.hxx>
|
|
#include <lang.hxx>
|
|
#include <qparse.hxx>
|
|
#include <cci.hxx>
|
|
#include <rowset.hxx>
|
|
#include <query.hxx>
|
|
#include <seqquery.hxx>
|
|
#include <qoptimiz.hxx>
|
|
#include <cifailte.hxx>
|
|
|
|
#include <dbprputl.hxx>
|
|
#include <dbprpini.hxx>
|
|
#include <ciframe.hxx>
|
|
#include <pidcvt.hxx>
|
|
#include <proprst.hxx>
|
|
|
|
static const GUID guidQuery = PSGUID_QUERY;
|
|
|
|
extern CLocateDocStore g_DocStoreLocator;
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::QueryInterface, public
|
|
//
|
|
// Arguments: [ifid] -- Interface id
|
|
// [ppiuk] -- Interface return pointer
|
|
//
|
|
// Returns: Error. No rebind from this class is supported.
|
|
//
|
|
// History: 01-Oct-92 KyleP Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
STDMETHODIMP CGenericQuery::QueryInterface( REFIID ifid, void ** ppiuk )
|
|
{
|
|
if ( IID_IUnknown == ifid )
|
|
{
|
|
*ppiuk = (void *)((IUnknown *)this);
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
*ppiuk = 0;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::AddRef, public
|
|
//
|
|
// Synopsis: Reference the virtual table.
|
|
//
|
|
// History: 01-Oct-92 KyleP Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
STDMETHODIMP_(ULONG) CGenericQuery::AddRef(void)
|
|
{
|
|
return InterlockedIncrement( (long *)&_ref );
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::Release, public
|
|
//
|
|
// Synopsis: De-Reference the virtual table.
|
|
//
|
|
// Effects: If the ref count goes to 0 then the table is deleted.
|
|
//
|
|
// History: 01-Oct-92 KyleP Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
STDMETHODIMP_(ULONG) CGenericQuery::Release(void)
|
|
{
|
|
long l = InterlockedDecrement( (long *)&_ref );
|
|
if ( l <= 0 )
|
|
{
|
|
// delete self
|
|
|
|
vqDebugOut(( DEB_ITRACE,
|
|
"FAT IInternalQuery unreferenced. Deleting.\n" ));
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::Execute, public
|
|
//
|
|
// Synopsis: Executes a query. Helper for ICommand::Execute.
|
|
//
|
|
// Arguments: [pUnkOuter] -- Outer unknown
|
|
// [pRestriction] -- Query restriction
|
|
// [pidmap] -- pid mapper for output, sort, category columns
|
|
// [rColumns] -- Output columns in IRowset
|
|
// [rSort] -- Initial sort
|
|
// [xProps] -- Rowset properties (query flags)
|
|
// [rCateg] -- Categorization specification
|
|
// [cRowsets] -- # of rowsets
|
|
// [ppUnknowns] -- IUnknown pointers returned here
|
|
// [aAccessors] -- Bag of accessors which rowsets need to inherit
|
|
//
|
|
// Returns: Throws on error
|
|
//
|
|
// History: 26 Nov 1995 AlanW Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CGenericQuery::Execute( IUnknown * pUnkOuter,
|
|
RESTRICTION * pRestriction,
|
|
CPidMapperWithNames & pidmap,
|
|
CColumnSet & rColumns,
|
|
CSortSet & rSort,
|
|
XPtr<CMRowsetProps> & xProps,
|
|
CCategorizationSet & rCateg,
|
|
ULONG cRowsets,
|
|
IUnknown ** ppUnknowns,
|
|
CAccessorBag & aAccessors,
|
|
IUnknown * pUnkCreator )
|
|
{
|
|
#if 1
|
|
Win4Assert( FALSE );
|
|
#else
|
|
|
|
SCODE sc = S_OK;
|
|
|
|
TRY
|
|
{
|
|
if (0 == ppUnknowns)
|
|
THROW( CException( E_INVALIDARG ));
|
|
|
|
if ( cRowsets != 1 + (rCateg.Count() ? rCateg.Count() : 0) )
|
|
THROW( CException( E_INVALIDARG ));
|
|
|
|
if (_QueryUnknown.IsQueryActive())
|
|
{
|
|
vqDebugOut(( DEB_ERROR,
|
|
"CGenericQuery: Query already active.\n" ));
|
|
|
|
// pec variance: only if query changed
|
|
|
|
THROW( CException( DB_E_OBJECTOPEN ));
|
|
}
|
|
|
|
//
|
|
// Parameter checking.
|
|
//
|
|
|
|
*ppUnknowns = 0; // in case of error or exception
|
|
|
|
//
|
|
// Convert parsed query into low-level query understood by
|
|
// the Query/CI engine. This process expands the query tree and
|
|
// also maps GUID\DISPID and GUID\NAME style property names to
|
|
// a ULONG pid.
|
|
//
|
|
|
|
//
|
|
// Duplicate the output column set.
|
|
//
|
|
|
|
XColumnSet ColDup( new CColumnSet ( rColumns.Count() ) );
|
|
|
|
for ( unsigned i = 0; i < rColumns.Count(); i++ )
|
|
{
|
|
ColDup->Add( rColumns.Get(i), i );
|
|
}
|
|
|
|
//
|
|
// get a pointer to CLangList
|
|
//
|
|
|
|
CLangList * pLangList = 0;
|
|
ICiManager * pICiManager = 0;
|
|
ICiFrameworkQuery * pICiFrameworkQuery = 0;
|
|
|
|
XInterface<ICiManager> xICiManager;
|
|
XInterface<ICiFrameworkQuery> xICiFrameworkQuery;
|
|
|
|
SCODE sc = _xDocStore->GetContentIndex(&pICiManager);
|
|
if ( FAILED(sc) )
|
|
{
|
|
THROW( CException(sc) );
|
|
}
|
|
else
|
|
{
|
|
xICiManager.Set(pICiManager);
|
|
}
|
|
|
|
sc = xICiManager->QueryInterface(IID_ICiFrameworkQuery,(void **)&pICiFrameworkQuery);
|
|
if ( FAILED(sc) )
|
|
{
|
|
THROW( CException(sc) );
|
|
}
|
|
else
|
|
{
|
|
xICiFrameworkQuery.Set(pICiFrameworkQuery);
|
|
}
|
|
|
|
sc = xICiFrameworkQuery->GetLangList( (void **) &pLangList);
|
|
if ( FAILED(sc) )
|
|
{
|
|
THROW( CException(sc) );
|
|
}
|
|
|
|
//
|
|
// end of getting a pointer to the langlist
|
|
//
|
|
|
|
//
|
|
// Set up a property mapper. Used for restriction parsing and sort/output
|
|
// column translation.
|
|
//
|
|
|
|
XInterface<IPropertyMapper> xPropMapper;
|
|
|
|
sc = _xDocStore->GetPropertyMapper( xPropMapper.GetPPointer() );
|
|
|
|
if ( FAILED( sc ) )
|
|
{
|
|
vqDebugOut(( DEB_ERROR, "CGenericQuery::Execute, GetPropertyMapper error 0x%x\n", sc ));
|
|
THROW( CException( sc ) );
|
|
}
|
|
|
|
//
|
|
// Adjust pidmap to translate properties. NOTE: Undone in CATCH for failure case.
|
|
//
|
|
|
|
CPidConverter PidConverter( xPropMapper.GetPointer() );
|
|
|
|
pidmap.SetPidConverter( &PidConverter );
|
|
|
|
XRestriction rstParsed;
|
|
DWORD dwQueryStatus = 0;
|
|
if ( pRestriction )
|
|
{
|
|
CQParse qparse( pidmap, *pLangList ); // Maps name to pid
|
|
|
|
rstParsed.Set( qparse.Parse( (CRestriction *)pRestriction ) );
|
|
|
|
DWORD dwParseStatus = qparse.GetStatus();
|
|
|
|
if ( ( 0 != ( dwParseStatus & CI_NOISE_PHRASE ) ) &&
|
|
( ((CRestriction *)pRestriction)->Type() != RTVector ) )
|
|
{
|
|
vqDebugOut(( DEB_WARN, "Query contains phrase composed "
|
|
"entirely of noise words.\n" ));
|
|
THROW( CException( QUERY_E_ALLNOISE ) );
|
|
}
|
|
|
|
const DWORD dwCiNoise = CI_NOISE_IN_PHRASE | CI_NOISE_PHRASE;
|
|
if ( 0 != ( dwCiNoise & dwParseStatus ) )
|
|
dwQueryStatus |= STAT_NOISE_WORDS;
|
|
}
|
|
|
|
//
|
|
// Duplicate the sort definition.
|
|
//
|
|
|
|
XSortSet SortDup;
|
|
|
|
// Only create a sort duplicate if we have a sort spec AND it
|
|
// actually contains anything.
|
|
|
|
if ( 0 != rSort.Count() )
|
|
{
|
|
SortDup.Set( new CSortSet ( rSort.Count() ) );
|
|
for ( unsigned i = 0; i < rSort.Count(); i++ )
|
|
{
|
|
SortDup->Add( rSort.Get(i), i );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Duplicate the categorization specification
|
|
//
|
|
|
|
XCategorizationSet CategDup;
|
|
|
|
if (cRowsets > 1)
|
|
CategDup.Set( new CCategorizationSet ( rCateg ) );
|
|
|
|
//
|
|
// Re-map property ids.
|
|
//
|
|
|
|
//
|
|
// TODO: Get rid of this whole pid remap thing. We should
|
|
// really be able to do it earlier now that the pidmap
|
|
// can be set up to convert fake to real pids.
|
|
//
|
|
|
|
XInterface<CPidRemapper> pidremap( new CPidRemapper( pidmap,
|
|
xPropMapper,
|
|
0, // rstParsed.GetPointer(),
|
|
ColDup.GetPointer(),
|
|
|
|
SortDup.GetPointer() ) );
|
|
|
|
//
|
|
// WorkID may be added to the columns requested in SetBindings.
|
|
// Be sure it's in the pidremap from the beginning.
|
|
//
|
|
CFullPropSpec psWorkId(guidQuery, DISPID_QUERY_WORKID);
|
|
pidremap->NameToReal( &psWorkId );
|
|
|
|
XInterface<PQuery> xQuery;
|
|
XArray<ULONG> aCursors(cRowsets);
|
|
|
|
ICiQueryPropertyMapper *pQueryPropMapper;
|
|
sc = pidremap->QueryInterface( IID_ICiQueryPropertyMapper,
|
|
(void **) &pQueryPropMapper );
|
|
if ( FAILED(sc) )
|
|
{
|
|
vqDebugOut(( DEB_ERROR, "DoCreateQuery - QI for property mapper failed 0x%x\n", sc ));
|
|
|
|
THROW ( CException( sc ) ) ;
|
|
}
|
|
|
|
XInterface<ICiQueryPropertyMapper> xQueryPropMapper( pQueryPropMapper );
|
|
|
|
ICiCQuerySession *pQuerySession;
|
|
SCODE scode = _xDocStore->GetQuerySession( &pQuerySession );
|
|
|
|
if ( FAILED(scode) )
|
|
{
|
|
vqDebugOut(( DEB_ERROR, "CGenericQuery::Execute - QI failed 0x%x\n", scode ));
|
|
|
|
THROW ( CException( scode ) ) ;
|
|
}
|
|
|
|
XInterface<ICiCQuerySession> xQuerySession( pQuerySession );
|
|
|
|
//
|
|
// Initialize the query session
|
|
//
|
|
xQuerySession->Init( pidmap.Count(),
|
|
(FULLPROPSPEC const * const *)pidmap.GetPointer(),
|
|
_xDbProperties.GetPointer(),
|
|
xQueryPropMapper.GetPointer() );
|
|
|
|
|
|
//
|
|
// Optimize query.
|
|
//
|
|
|
|
CRowsetProperties Props;
|
|
Props.SetDefaults( xProps->GetPropertyFlags(),
|
|
xProps->GetMaxOpenRows(),
|
|
xProps->GetMemoryUsage(),
|
|
xProps->GetMaxResults(),
|
|
xProps->GetCommandTimeout() );
|
|
|
|
XQueryOptimizer xqopt( new CQueryOptimizer( xQuerySession,
|
|
_xDocStore.GetPointer(),
|
|
rstParsed,
|
|
ColDup.GetReference(),
|
|
SortDup.GetPointer(),
|
|
pidremap.GetReference(),
|
|
Props,
|
|
dwQueryStatus ) );
|
|
|
|
vqDebugOut(( DEB_ITRACE, "Query has %s1 component%s\n",
|
|
xqopt->IsMultiCursor() ? "> " : "",
|
|
xqopt->IsMultiCursor() ? "(s)" : "" ));
|
|
vqDebugOut(( DEB_ITRACE, "Current component of query %s fully sorted\n",
|
|
xqopt->IsFullySorted() ? "is" : "is not" ));
|
|
vqDebugOut(( DEB_ITRACE, "Current component of query %s positionable\n",
|
|
xqopt->IsPositionable() ? "is" : "is not" ));
|
|
|
|
if ( (xProps->GetPropertyFlags() & eLocatable) == 0 )
|
|
{
|
|
//
|
|
// If all components are fully sorted, then
|
|
// it doesn't matter how many there are. A
|
|
// merge could be done. But be careful, you
|
|
// need an ordering even with a null sort.
|
|
//
|
|
// Categorized queries must go through bigtable for now
|
|
// If someday we support categorizations that don't require
|
|
// sorting then change this check.
|
|
//
|
|
if ( ( !xqopt->IsMultiCursor() ) &&
|
|
( xqopt->IsFullySorted() ) &&
|
|
( 1 == cRowsets ) )
|
|
{
|
|
xQuery.Set( new CSeqQuery( xqopt,
|
|
ColDup,
|
|
aCursors.GetPointer(),
|
|
pidremap,
|
|
_xDocStore.GetPointer() ) );
|
|
}
|
|
else
|
|
{
|
|
xQuery.Set( new CAsyncQuery( xqopt,
|
|
ColDup,
|
|
SortDup,
|
|
CategDup,
|
|
cRowsets,
|
|
aCursors.GetPointer(),
|
|
pidremap,
|
|
FALSE,
|
|
_xDocStore.GetPointer(),
|
|
0 ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xQuery.Set( new CAsyncQuery( xqopt,
|
|
ColDup,
|
|
SortDup,
|
|
CategDup,
|
|
cRowsets,
|
|
aCursors.GetPointer(),
|
|
pidremap,
|
|
(xProps->GetPropertyFlags() & eWatchable) != 0,
|
|
_xDocStore.GetPointer(),
|
|
0 ) );
|
|
}
|
|
|
|
//
|
|
// If we've been instructed to create a non-asynchronous cursor,
|
|
// then we don't return to the caller until we have the first
|
|
// row. In the case of a sorted sequential cursor (which is
|
|
// implemented with a table), we wait until the query is
|
|
// complete.
|
|
//
|
|
|
|
if ( (xProps->GetPropertyFlags() & eAsynchronous) == 0 )
|
|
{
|
|
//
|
|
// An event would be better than these Sleeps...
|
|
//
|
|
|
|
ULONG sleepTime = 500;
|
|
|
|
while ( TRUE )
|
|
{
|
|
DBCOUNTITEM ulDenominator;
|
|
DBCOUNTITEM ulNumerator;
|
|
DBCOUNTITEM cRows;
|
|
BOOL fNewRows;
|
|
xQuery->RatioFinished( 0,
|
|
ulDenominator,
|
|
ulNumerator,
|
|
cRows,
|
|
fNewRows );
|
|
|
|
if ( ulNumerator == ulDenominator )
|
|
break;
|
|
|
|
Sleep( sleepTime );
|
|
|
|
sleepTime *= 2;
|
|
sleepTime = min( sleepTime, 4000 );
|
|
}
|
|
}
|
|
|
|
// Make rowsets for each level in the hierarchy (usually 1).
|
|
// Rowset 0 is the top of the hierarchy.
|
|
|
|
_QueryUnknown.ReInit();
|
|
CRowsetArray apRowsets( cRowsets );
|
|
XArray<IUnknown *> xapUnknowns( cRowsets );
|
|
|
|
CMRowsetProps & OrigProps = xProps.GetReference();
|
|
|
|
#ifdef CI_FAILTEST
|
|
NTSTATUS failStatus = CI_CORRUPT_CATALOG;
|
|
ciFAILTEST( failStatus );
|
|
#endif // CI_FAILTEST
|
|
|
|
for (unsigned r = 0; r < cRowsets; r++)
|
|
{
|
|
XPtr<CMRowsetProps> xTmp;
|
|
|
|
if ( 0 != r )
|
|
{
|
|
xTmp.Set( new CMRowsetProps( OrigProps ) );
|
|
xTmp->SetChaptered( TRUE );
|
|
}
|
|
|
|
if ( 1 != cRowsets && 0 == r )
|
|
xProps->SetChaptered( FALSE );
|
|
|
|
apRowsets[r] = new CRowset( pUnkOuter,
|
|
&xapUnknowns[r],
|
|
(r == (cRowsets - 1)) ?
|
|
rColumns :
|
|
rCateg.Get(r)->GetColumnSet(),
|
|
pidmap,
|
|
xQuery.GetReference(),
|
|
(IUnknown &) _QueryUnknown,
|
|
0 != r,
|
|
( 0 == r ) ? xProps : xTmp,
|
|
aCursors[r],
|
|
aAccessors,
|
|
pUnkCreator );
|
|
}
|
|
|
|
for (r = 0; r < cRowsets; r++)
|
|
{
|
|
if (r < cRowsets-1)
|
|
apRowsets[r]->SetRelatedRowset( apRowsets[r+1] );
|
|
ppUnknowns[r] = (IRowset *) xapUnknowns[r];
|
|
}
|
|
|
|
// xQuery goes out of scope, doing a Release() on it.
|
|
// Each rowset above has done an AddRef() on it and they own it.
|
|
|
|
_QueryUnknown.ReInit( cRowsets, apRowsets.Acquire() );
|
|
}
|
|
CATCH( CException,e )
|
|
{
|
|
ciDebugOut(( DEB_ERROR,
|
|
"Error 0x%X while executing query\n",
|
|
e.GetErrorCode() ));
|
|
|
|
pidmap.SetPidConverter( 0 );
|
|
|
|
ICiManager *pCiManager = 0;
|
|
sc = _xDocStore->GetContentIndex( &pCiManager );
|
|
if ( SUCCEEDED( sc ) )
|
|
{
|
|
//
|
|
// GetContentIndex may fail during shutdown
|
|
//
|
|
ICiFrameworkQuery * pIFwQuery = 0;
|
|
|
|
sc = pCiManager->QueryInterface( IID_ICiFrameworkQuery,
|
|
(void **) &pIFwQuery );
|
|
pCiManager->Release();
|
|
if ( pIFwQuery )
|
|
{
|
|
pIFwQuery->ProcessError( e.GetErrorCode() );
|
|
pIFwQuery->Release();
|
|
}
|
|
}
|
|
|
|
RETHROW();
|
|
}
|
|
END_CATCH
|
|
|
|
pidmap.SetPidConverter( 0 );
|
|
#endif
|
|
} //Execute
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::CGenericQuery, public
|
|
//
|
|
// Synopsis: Opens file system directory for query
|
|
//
|
|
// Arguments: [pDbProperties] -- Properties to be used for this query
|
|
//
|
|
// History: 18-Jun-93 KyleP Created
|
|
// 22-Apr-97 KrishnaN Changed header block
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CGenericQuery::CGenericQuery( IDBProperties * pDbProperties )
|
|
: PIInternalQuery( 0 ),
|
|
#pragma warning(disable : 4355) // 'this' in a constructor
|
|
_QueryUnknown( * ((IUnknown *) this) )
|
|
#pragma warning(default : 4355) // 'this' in a constructor
|
|
{
|
|
pDbProperties->AddRef();
|
|
_xDbProperties.Set( pDbProperties );
|
|
|
|
//
|
|
// Locate the DocStore for the given set of properties.
|
|
//
|
|
ICiCDocStoreLocator * pLocator = g_DocStoreLocator.Get();
|
|
|
|
if ( 0 == pLocator )
|
|
{
|
|
//
|
|
// Determine the GUID of the docstore locator from the query and
|
|
// use it.
|
|
//
|
|
CGetDbProps dbProps;
|
|
dbProps.GetProperties( pDbProperties,
|
|
CGetDbProps::eClientGuid );
|
|
|
|
GUID const * pGuidClient = dbProps.GetClientGuid();
|
|
if ( 0 == pGuidClient )
|
|
{
|
|
ciDebugOut(( DEB_ERROR, "The DocStoreLocator Guid is invalid\n" ));
|
|
THROW( CException( E_INVALIDARG ) );
|
|
}
|
|
|
|
pLocator = g_DocStoreLocator.Get( *pGuidClient );
|
|
}
|
|
|
|
Win4Assert( pLocator != 0 );
|
|
|
|
XInterface<ICiCDocStoreLocator> xLocator( pLocator );
|
|
|
|
ICiCDocStore *pDocStore;
|
|
SCODE sc = xLocator->LookUpDocStore( pDbProperties, &pDocStore, TRUE );
|
|
if ( FAILED(sc) )
|
|
{
|
|
ciDebugOut(( DEB_ITRACE, "Failed to locate doc store. Error (0x%X)\n",
|
|
sc ));
|
|
THROW( CException(sc) );
|
|
}
|
|
|
|
Win4Assert( pDocStore != 0 );
|
|
_xDocStore.Set( pDocStore );
|
|
|
|
AddRef();
|
|
} //CGenericQuery
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::CGenericQuery, public
|
|
//
|
|
// Synopsis: Opens file system directory for query
|
|
//
|
|
// Arguments: [pDbProperties] -- Properties to be used for this query
|
|
// [pDocStore] -- Docstore to query against
|
|
//
|
|
// History: 22-Apr-97 KrishnaN Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CGenericQuery::CGenericQuery( IDBProperties * pDbProperties,
|
|
ICiCDocStore * pDocStore )
|
|
: PIInternalQuery( 0 ),
|
|
#pragma warning(disable : 4355) // 'this' in a constructor
|
|
_QueryUnknown( * ((IUnknown *) this) )
|
|
#pragma warning(default : 4355) // 'this' in a constructor
|
|
{
|
|
pDbProperties->AddRef();
|
|
_xDbProperties.Set( pDbProperties );
|
|
|
|
Win4Assert( pDocStore != 0 );
|
|
pDocStore->AddRef();
|
|
_xDocStore.Set( pDocStore );
|
|
|
|
AddRef();
|
|
} //CGenericQuery
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CGenericQuery::~CGenericQuery, public
|
|
//
|
|
// Synopsis: Required virtual destructor
|
|
//
|
|
// History: 17-Jul-93 KyleP Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CGenericQuery::~CGenericQuery()
|
|
{
|
|
}
|
|
|