2025-04-27 07:49:33 -04:00

819 lines
19 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
qrycache.cxx
Abstract:
This file contains the code for caching ODBC queries
Author:
John Ludeman (johnl) 27-Jun-1995
Revision History:
--*/
# include "dbgutil.h"
#include <tcpdll.hxx>
#include <tsunami.hxx>
#include <odbcconn.hxx>
#include <parmlist.hxx>
#include <odbcmsg.h>
#include <odbcreq.hxx>
#include <qrycache.hxx>
#include <decnotif.hxx>
//
// Manifests
//
//
// Query Cache lock. Controls access to all items
//
#define LockQueryCache() EnterCriticalSection( &csQueryCacheLock )
#define UnlockQueryCache() LeaveCriticalSection( &csQueryCacheLock )
//
// Global data
//
//
// Gets incremented everytime any cached .wdg file or .htx file changes. Acts
// as a serial number so we can avoid holding the critical section across
// queries
//
DWORD cCacheChangeCounter;
//
// Locks the Query Cache List and Decache Notification List
//
CRITICAL_SECTION csQueryCacheLock;
//
// Private prototypes
//
BOOL
FreeQCLCacheBlob(
VOID * pvCacheBlob
);
//
// An instance of a particular query with unique parameters. Note this
// object does not deal with its _ListEntry, it relies on the container
// object.
//
class QUERY_CACHE_ITEM
{
public:
QUERY_CACHE_ITEM( ODBC_REQ * podbcreq );
~QUERY_CACHE_ITEM();
ODBC_REQ * QueryQuery( VOID ) const
{ return _podbcreq; }
BOOL IsQueryEqual( ODBC_REQ * podbcreq ) const
{ return _podbcreq->IsEqual( podbcreq ); }
DWORD QueryAllocatedBytes( VOID ) const
{ return _podbcreq->QueryAllocatedBytes(); }
BOOL CheckSignature( VOID ) const
{ return _Signature == SIGNATURE_QCI; }
BOOL IsExpired( DWORD csecSysUpTime )
{ return _podbcreq->IsExpired( csecSysUpTime ); }
VOID RemoveFromList( BOOL fRemoveNotif )
{
TCP_ASSERT( CheckSignature() );
//
// Remove this cache item from the list and set its flink to NULL.
// This indicates the item should not be removed from the cache
// list again
//
RemoveEntryList( &_ListEntry );
_ListEntry.Flink = NULL;
//
// Remove and free the notification for this query
//
if ( fRemoveNotif && _pHTXNotification )
{
TCP_REQUIRE( RemoveDecacheNotification( _pHTXNotification ));
_pHTXNotification = NULL;
}
//
// Dereference this cache item. If nobody has it checked out, this
// will delete the item, otherwise it will be deleted when all
// clients of this query check it back in
//
QUERY_CACHE_ITEM::Dereference( this );
}
static VOID Reference( QUERY_CACHE_ITEM * pQCI )
{
TCP_ASSERT( pQCI->CheckSignature() );
TCP_ASSERT( pQCI->_cRef > 0 );
pQCI->_cRef++;
}
static VOID Dereference( QUERY_CACHE_ITEM * pQCI )
{
TCP_ASSERT( pQCI->CheckSignature() );
TCP_ASSERT( pQCI->_cRef > 0 );
if ( !(--pQCI->_cRef) )
{
delete pQCI;
}
}
DWORD _Signature;
ODBC_REQ * _podbcreq;
LIST_ENTRY _ListEntry; // If Flink == NULL, not on cache list
DWORD _cRef; // When _cRef == 0, delete this item
VOID * _pHTXNotification;
};
//
// This is the contents of the cache blob for the .wdg file. It contains the
// list of queries
//
class QUERY_CACHE_LIST : public BLOB_HEADER
{
public:
QUERY_CACHE_LIST()
: _Signature( SIGNATURE_QCL )
{ InitializeListHead( &_QueryListHead ); }
~QUERY_CACHE_LIST()
{
DeleteAllItems();
_Signature = SIGNATURE_QCL_FREE;
}
BOOL FindQuery( IN ODBC_REQ * podbcreq,
OUT QUERY_CACHE_ITEM * * ppQCI );
BOOL AddQuery( IN QUERY_CACHE_ITEM * pQCI );
VOID DeleteAllItems( VOID );
BOOL CheckSignature( VOID ) const
{ return _Signature == SIGNATURE_QCL; }
//
// Memory for a QCL comes from Tsunami's cache manager
//
static void * operator new( size_t size,
void * pMem )
{
TCP_ASSERT( size == sizeof(QUERY_CACHE_LIST));
return pMem;
}
static void operator delete( void * pMem )
{
//
// Tsunami frees this memory
//
;
}
private:
LIST_ENTRY _QueryListHead;
DWORD _Signature;
};
BOOL
AddQuery(
IN ODBC_REQ * podbcreq,
IN DWORD CurChangeCounter
)
/*++
Routine Description:
Adds a complete ODBC query to the query cache list that is hanging off
the directory cache blob for this query file. If a query cache list
doesn't exist, then we add it.
Arguments:
podbcreq - Query to cache
CurChangeCounter - The change counter value before the query file,
template file were openned and the query was performed. If any
files have changed since then, we do not cache this particular
request.
Return Value:
TRUE if this query was cached, FALSE if the query was not cached
--*/
{
QUERY_CACHE_LIST * pQCL;
QUERY_CACHE_ITEM * pQCI = NULL;
VOID * pvNotifCookie = NULL;
VOID * pvCheckinCookie = NULL;
TSVC_CACHE CacheID = *podbcreq->QueryTsvcCache();
const ULONG cchQueryFile = strlen(podbcreq->QueryQueryFile());
LockQueryCache();
//
// If we detected any changes in .htx or .wdg files since we openned up
// the component files and performed the query then don't cache
// this query
//
if ( CurChangeCounter != GetChangeCounter() )
{
UnlockQueryCache();
return FALSE;
}
UnlockQueryCache();
//
// Get the QCL cache blob
//
if ( !TsCheckOutCachedBlob( CacheID,
podbcreq->QueryQueryFile(),
cchQueryFile,
RESERVED_DEMUX_QUERY_CACHE,
(VOID **) &pQCL,
podbcreq->QueryUserToken(),
podbcreq->IsAnonymous() ))
{
if ( GetLastError() == ERROR_ACCESS_DENIED )
{
return FALSE;
}
VOID * pvMem;
//
// The Query Cache List isn't in the Tsunami cache so create it
// and add it
//
if ( !TsAllocateEx( CacheID,
sizeof( QUERY_CACHE_LIST ),
&pvMem,
(PUSER_FREE_ROUTINE) FreeQCLCacheBlob ))
{
//
// Failed to cache Query Cache List so bail
//
return FALSE;
}
pQCL = new(pvMem) QUERY_CACHE_LIST();
TCP_ASSERT( pQCL );
//
// Now attempt to add it as a cache blob and check it out at the
// same time
//
if ( !TsCacheDirectoryBlob( CacheID,
podbcreq->QueryQueryFile(),
cchQueryFile,
RESERVED_DEMUX_QUERY_CACHE,
pQCL,
TRUE,
podbcreq->GetSecDesc() ))
{
//
// Failed to cache Query Cache List so bail
//
//
// This will end up calling FreeQCLCacheBlob where the
// object gets deconstructed
//
TCP_REQUIRE( TsFree( CacheID,
pvMem ));
return FALSE;
}
else
{
//
// ownership of security descriptor has been transferred to the
// directory blob cache.
//
podbcreq->InvalidateSecDesc();
}
}
TCP_ASSERT( pQCL->CheckSignature() );
//
// Now that we've successfully checked out the QCL,
// add this particular query to the list
//
LockQueryCache();
//
// Make sure this query isn't already in the list.
//
if ( pQCL->FindQuery( podbcreq,
&pQCI ))
{
//
// This query is already in the list.
//
TCP_REQUIRE( TsCheckInCachedBlob( pQCL ));
UnlockQueryCache();
return FALSE;
}
//
// Create the query cache item but don't put it on the list till we're
// done with everything else as the list contents can get destroyed by
// adding the decache notification (i.e., things get thrown out of cache
// due to new item being added).
//
pQCI = new QUERY_CACHE_ITEM( podbcreq );
if ( !pQCI )
{
TCP_REQUIRE( TsCheckInCachedBlob( pQCL ));
UnlockQueryCache();
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
//
// We now own the podbcreq object as it's on our list. We can't return
// FALSE after this point otherwise podbcreq will get deleted twice
// (once by caller, once because it's in our list)
//
if ( !CheckOutDecacheNotification( &CacheID,
podbcreq->QueryTemplateFile(),
DecacheTemplateNotification,
pQCI,
RESERVED_DEMUX_QUERY_CACHE,
&csQueryCacheLock,
&pQCI->_pHTXNotification,
&pvCheckinCookie ))
{
TCP_ASSERT( pQCL->CheckSignature());
TCP_ASSERT( pQCI->CheckSignature());
//
// We couldn't add this query, clean things up
//
if ( pQCI->_pHTXNotification )
{
CheckInDecacheNotification( &CacheID,
pQCI->_pHTXNotification,
pvCheckinCookie,
FALSE );
pQCI->_pHTXNotification = NULL;
}
TCP_ASSERT( pQCI->_cRef == 1 );
QUERY_CACHE_ITEM::Dereference( pQCI );
TCP_REQUIRE( TsCheckInCachedBlob( pQCL ));
UnlockQueryCache();
return TRUE;
}
//
// Finally, add the item to the lists and check them in
//
pQCL->AddQuery( pQCI );
CheckInDecacheNotification( &CacheID,
pQCI->_pHTXNotification,
pvCheckinCookie,
TRUE );
UnlockQueryCache();
//
// Check in the list
//
TCP_REQUIRE( TsCheckInCachedBlob( (VOID *) pQCL ));
return TRUE;
}
BOOL
CheckOutQuery(
IN ODBC_REQ * podbcreq,
OUT VOID * * ppvCacheCookie,
OUT ODBC_REQ * * ppodbcreqCached
)
/*++
Routine Description:
Checks to see if the specified query is in our cache. If it is,
we return the equivalent cached query.
Arguments:
podbcreq - Query to check to see if it's in the cache
ppvCacheCookie - Receives pointer to Item cookie (pointer to Query Cache
Item)
ppodbcreqCached - If the query was previously cached, this receives the
cached query
Return Value:
TRUE if the query was found and checked out, FALSE if the query could
not be checked (not found, error occurred etc)
--*/
{
QUERY_CACHE_LIST * pQCL;
QUERY_CACHE_ITEM * pQCI;
BOOL fRet = FALSE;
//
// Get the QCL cache blob
//
if ( TsCheckOutCachedBlob( *podbcreq->QueryTsvcCache(),
podbcreq->QueryQueryFile(),
strlen(podbcreq->QueryQueryFile()),
RESERVED_DEMUX_QUERY_CACHE,
(VOID **) &pQCL,
podbcreq->QueryUserToken(),
podbcreq->IsAnonymous() ))
{
TCP_ASSERT( pQCL->CheckSignature() );
//
// See if the query already exists
//
LockQueryCache();
fRet = pQCL->FindQuery( podbcreq,
(QUERY_CACHE_ITEM **) ppvCacheCookie );
if ( fRet )
{
//
// Bump the ref count until the client checks this query back in
//
QUERY_CACHE_ITEM::Reference( (QUERY_CACHE_ITEM *) *ppvCacheCookie );
*ppodbcreqCached = ((QUERY_CACHE_ITEM *) *ppvCacheCookie)->QueryQuery();
}
UnlockQueryCache();
//
// Check in the query list
//
TCP_REQUIRE( TsCheckInCachedBlob( pQCL ));
}
return fRet;
}
BOOL
CheckInQuery(
IN VOID * pvCacheCookie
)
/*++
Routine Description:
Checks in a query cache item as the client no longer needs it
Arguments:
pQCI - Previously checked out query
Return Value:
TRUE if successful, FALSE on error
--*/
{
TCP_ASSERT( ((QUERY_CACHE_ITEM *) pvCacheCookie)->CheckSignature() );
QUERY_CACHE_ITEM::Dereference( (QUERY_CACHE_ITEM *) pvCacheCookie );
return TRUE;
}
BOOL
InitializeQueryCache(
VOID
)
{
INITIALIZE_CRITICAL_SECTION( &csQueryCacheLock );
return TRUE;
}
VOID
TerminateQueryCache(
VOID
)
{
//
// Flush Tsunami blobs associated with this extension
//
TsCacheFlushDemux( RESERVED_DEMUX_QUERY_CACHE );
DeleteCriticalSection( &csQueryCacheLock );
}
BOOL
QUERY_CACHE_LIST::FindQuery(
IN ODBC_REQ * podbcreq,
OUT QUERY_CACHE_ITEM * * ppQCI
)
/*++
Routine Description:
Looks in the this cache list for the specified query
The Query cache lock must be taken before calling this method
Arguments:
podbcreq - Query template, used for checking if two queries are equal
ppQCI - Receives pointer to the cached query item if found
Return Value:
TRUE if the query was found, FALSE if the query was not found
--*/
{
LIST_ENTRY * pEntry;
QUERY_CACHE_ITEM * pQCI;
DWORD TickCount = GetTickCount() / 1000;
TCP_ASSERT( CheckSignature() );
for ( pEntry = _QueryListHead.Flink;
pEntry != &_QueryListHead;
)
{
pQCI = CONTAINING_RECORD( pEntry, QUERY_CACHE_ITEM, _ListEntry );
TCP_ASSERT( pQCI->CheckSignature() );
//
// Make sure we don't deliver expired queries
//
if ( pQCI->IsExpired( TickCount ) )
{
pEntry = pEntry->Flink;
pQCI->RemoveFromList( TRUE );
continue;
}
if ( pQCI->IsQueryEqual( podbcreq ) )
{
*ppQCI = pQCI;
//
// Move this query to the front of the list if it's not already
//
if ( _QueryListHead.Flink != pEntry )
{
RemoveEntryList( pEntry );
InsertHeadList( &_QueryListHead,
pEntry );
}
return TRUE;
}
pEntry = pEntry->Flink;
}
return FALSE;
}
BOOL
QUERY_CACHE_LIST::AddQuery(
IN QUERY_CACHE_ITEM * pQCI
)
/*++
Routine Description:
Adds the specified query to the cache list
The Query cache lock must be taken before calling this method
Arguments:
podbcreq - Query template
Return Value:
TRUE if the query was found, FALSE if the query was not found
--*/
{
InsertHeadList( &_QueryListHead,
&pQCI->_ListEntry );
return TRUE;
}
VOID
QUERY_CACHE_LIST::DeleteAllItems(
VOID
)
/*++
Routine Description:
Removes all items from this query cache list
Arguments:
--*/
{
LIST_ENTRY * pEntry;
QUERY_CACHE_ITEM * pQCI;
TCP_ASSERT( CheckSignature() );
while ( !IsListEmpty( &_QueryListHead ))
{
pQCI = CONTAINING_RECORD( _QueryListHead.Flink,
QUERY_CACHE_ITEM,
_ListEntry );
pQCI->RemoveFromList( TRUE );
}
}
QUERY_CACHE_ITEM::QUERY_CACHE_ITEM(
ODBC_REQ * podbcreq
)
: _Signature ( SIGNATURE_QCI ),
_cRef ( 1 ),
_podbcreq ( podbcreq ),
_pHTXNotification( NULL )
{
_ListEntry.Flink = NULL;
}
QUERY_CACHE_ITEM::~QUERY_CACHE_ITEM()
{
TCP_ASSERT( _ListEntry.Flink == NULL );
TCP_ASSERT( _cRef == 0 );
delete _podbcreq;
_Signature = SIGNATURE_QCI_FREE;
}
BOOL
FreeQCLCacheBlob(
VOID * pvCacheBlob
)
/*++
Routine Description:
This is the routine that is called by the Tsunami cache manager when
the query file (i.e., .wdg) has been changed. We're being notified
that this cache blob is about to be removed from the cache and deleted
Arguments:
pvCacheBlob - Cache blob about to be deleted
--*/
{
QUERY_CACHE_LIST * pQCL = (QUERY_CACHE_LIST *) pvCacheBlob;
TCP_ASSERT( pQCL->CheckSignature() );
LockQueryCache();
pQCL->DeleteAllItems();
//
// Deconstruct the list object, this doesn't free the memory
//
delete pQCL;
//
// Bump the change counter cause a .wdg file has changed
//
cCacheChangeCounter++;
//
// We don't need to free pQCL because it's the cache that's about to
// be deleted
//
UnlockQueryCache();
return TRUE;
}
VOID
DecacheTemplateNotification(
VOID * pvContext
)
/*++
Routine Description:
This is the routine that is called by the notification package when the
template file this query is using has changed. Note the query cache lock
has already been taken by the notification code
Arguments:
pvContext - Notification context
--*/
{
QUERY_CACHE_ITEM * pQCI = (QUERY_CACHE_ITEM *) pvContext;
TCP_ASSERT( pQCI->CheckSignature() );
//
// Take this item off the query cache list
//
TCP_ASSERT( pQCI->_ListEntry.Flink != NULL );
//
// Note we do not remove the notification because it's the
// notification that's called us. It will remove itself
//
pQCI->RemoveFromList( FALSE );
//
// Bump the change counter cause an .htx file has changed
//
cCacheChangeCounter++;
}