Leaked source code of windows server 2003
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.
 
 
 
 
 
 

10249 lines
282 KiB

//--------------------------------------------------------------------------
// Database.cpp
//--------------------------------------------------------------------------
#include "pch.hxx"
#include "database.h"
#include "stream.h"
#include "types.h"
#include "listen.h"
#include "resource.h"
#include "shlwapi.h"
#include "strconst.h"
#include "query.h"
#include "wrapwide.h"
//--------------------------------------------------------------------------
// Use Heap and/or Cache
//--------------------------------------------------------------------------
//#ifndef DEBUG
#define USEHEAP 1
#define HEAPCACHE 1
//#endif // DEBUG
//--------------------------------------------------------------------------
// Storage Block Access Macros
//--------------------------------------------------------------------------
#define PSTRING(_pBlock) ((LPSTR)((LPBYTE)_pBlock + sizeof(BLOCKHEADER)))
#define PUSERDATA(_pHeader) (LPBYTE)((LPBYTE)_pHeader + sizeof(TABLEHEADER))
//--------------------------------------------------------------------------
// g_rgcbBlockSize - Block Sizes
//--------------------------------------------------------------------------
const static WORD g_rgcbBlockSize[BLOCK_LAST] = {
sizeof(RECORDBLOCK), // BLOCK_RECORD
sizeof(BLOCKHEADER), // BLOCK_FILTER
0, // BLOCK_RESERVED1
sizeof(TRANSACTIONBLOCK), // BLOCK_TRANSACTION
sizeof(CHAINBLOCK), // BLOCK_CHAIN
sizeof(STREAMBLOCK), // BLOCK_STREAM
sizeof(FREEBLOCK), // BLOCK_FREE
sizeof(BLOCKHEADER), // BLOCK_ENDOFPAGE
0, // BLOCK_RESERVED2
0 // BLOCK_RESERVED3
};
//--------------------------------------------------------------------------
// ZeroBlock
//--------------------------------------------------------------------------
inline void ZeroBlock(LPBLOCKHEADER pBlock, DWORD cbSize) {
ZeroMemory((LPBYTE)pBlock + sizeof(BLOCKHEADER), cbSize - sizeof(BLOCKHEADER));
}
//--------------------------------------------------------------------------
// CopyBlock
//--------------------------------------------------------------------------
inline void CopyBlock(LPBLOCKHEADER pDest, LPBLOCKHEADER pSource, DWORD cbSize) {
CopyMemory((LPBYTE)pDest + sizeof(BLOCKHEADER), (LPBYTE)pSource + sizeof(BLOCKHEADER), cbSize - sizeof(BLOCKHEADER));
}
//--------------------------------------------------------------------------
// SafeFreeBinding
//--------------------------------------------------------------------------
#define SafeFreeBinding(_pBinding) \
if (_pBinding) { \
FreeRecord(_pBinding); \
HeapFree(_pBinding); \
_pBinding = NULL; \
} else
//--------------------------------------------------------------------------
// SafeHeapFree
//--------------------------------------------------------------------------
#define SafeHeapFree(_pvBuffer) \
if (_pvBuffer) { \
HeapFree(_pvBuffer); \
_pvBuffer = NULL; \
} else
//--------------------------------------------------------------------------
// UnmapViewOfFileWithFlush
//--------------------------------------------------------------------------
inline void UnmapViewOfFileWithFlush(BOOL fFlush, LPVOID pView, DWORD cbView)
{
// If we have a view
if (pView)
{
// Flush It ?
if (fFlush)
{
// Flush
SideAssert(0 != FlushViewOfFile(pView, cbView));
}
// UnMap It
SideAssert(0 != UnmapViewOfFile(pView));
}
}
//--------------------------------------------------------------------------
// PTagFromOrdinal
//--------------------------------------------------------------------------
inline LPCOLUMNTAG PTagFromOrdinal(LPRECORDMAP pMap, COLUMNORDINAL iColumn)
{
// Locals
LONG lLower=0;
LONG lUpper=pMap->cTags-1;
LONG lCompare;
WORD wMiddle;
LPCOLUMNTAG pTag;
// Do binary search / insert
while (lLower <= lUpper)
{
// Set lMiddle
wMiddle = (WORD)((lLower + lUpper) / 2);
// Compute middle record to compare against
pTag = &pMap->prgTag[(WORD)wMiddle];
// Get string to compare against
lCompare = (iColumn - pTag->iColumn);
// If Equal, then were done
if (lCompare == 0)
return(pTag);
// Compute upper and lower
if (lCompare > 0)
lLower = (LONG)(wMiddle + 1);
else
lUpper = (LONG)(wMiddle - 1);
}
// Not Found
return(NULL);
}
//--------------------------------------------------------------------------
// CDatabase::CDatabase
//--------------------------------------------------------------------------
CDatabase::CDatabase(void)
{
TraceCall("CDatabase::CDatabase");
Assert(9404 == sizeof(TABLEHEADER));
IF_DEBUG(DWORD dw);
IF_DEBUG(dw = offsetof(TABLEHEADER, rgdwReserved2));
IF_DEBUG(dw = offsetof(TABLEHEADER, rgIndexInfo));
IF_DEBUG(dw = offsetof(TABLEHEADER, rgdwReserved3));
Assert(offsetof(TABLEHEADER, rgdwReserved2) == 444);
Assert(offsetof(TABLEHEADER, rgIndexInfo) == 8892);
Assert(offsetof(TABLEHEADER, rgdwReserved3) == 9294);
m_cRef = 1;
m_cExtRefs = 0;
m_pSchema = NULL;
m_pStorage = NULL;
m_pShare = NULL;
m_hMutex = NULL;
m_pHeader = NULL;
m_hHeap = NULL;
m_fDeconstruct = FALSE;
m_fInMoveFile = FALSE;
m_fExclusive = FALSE;
m_dwProcessId = GetCurrentProcessId();
m_dwQueryVersion = 0;
m_pExtension = NULL;
m_pUnkRelease = NULL;
m_fDirty = FALSE;
#ifdef BACKGROUND_MONITOR
m_hMonitor = NULL;
#endif
m_fCompactYield = FALSE;
ZeroMemory(m_rgpRecycle, sizeof(m_rgpRecycle));
ZeroMemory(m_rghFilter, sizeof(m_rghFilter));
InitializeCriticalSection(&m_csHeap);
IF_DEBUG(m_cbHeapFree = m_cbHeapAlloc = 0);
ListenThreadAddRef();
DllAddRef();
}
//--------------------------------------------------------------------------
// CDatabase::~CDatabase
//--------------------------------------------------------------------------
CDatabase::~CDatabase(void)
{
// Locals
DWORD cClients=0;
DWORD i;
// Trace
TraceCall("CDatabase::~CDatabase");
// Release the Extension
SafeRelease(m_pUnkRelease);
// Decrement Thread Count
if (NULL == m_hMutex)
goto exit;
#ifdef BACKGROUND_MONITOR
// UnRegister...
if (m_hMonitor)
{
// Unregister
SideAssert(SUCCEEDED(UnregisterFromMonitor(this, &m_hMonitor)));
}
#endif
// Wait for the Mutex
WaitForSingleObject(m_hMutex, INFINITE);
// If I have a m_pShare
if (m_pStorage)
{
// If we have an m_pShare
if (m_pShare)
{
// Decrement the thread Count
if (m_pHeader && m_pHeader->cActiveThreads > 0)
{
// Decrement Thread Count
m_pHeader->cActiveThreads--;
}
// Set State that we are de-constructing
m_fDeconstruct = TRUE;
// Remove Client From Array
SideAssert(SUCCEEDED(_RemoveClientFromArray(m_dwProcessId, (DWORD_PTR)this)));
// Save Client Count
cClients = m_pShare->cClients;
// No more client
Assert(0 == cClients ? 0 == m_pShare->Rowsets.cUsed : TRUE);
}
// _CloseFileViews
_CloseFileViews(FALSE);
// Close the File
SafeCloseHandle(m_pStorage->hMap);
// Unmap the view of the memory mapped file
SafeUnmapViewOfFile(m_pShare);
// Unmap the view of the memory mapped file
SafeCloseHandle(m_pStorage->hShare);
// Close the File
if(m_pStorage->hFile /*&& m_fDirty*/)
{
FILETIME systime;
GetSystemTimeAsFileTime(&systime);
SetFileTime(m_pStorage->hFile, NULL, &systime, &systime);
}
SafeCloseHandle(m_pStorage->hFile);
// Free the mapping name
SafeMemFree(m_pStorage->pszMap);
// Free m_pStorage
SafeMemFree(m_pStorage);
}
// Close all the Query Handles
for (i=0; i<CMAX_INDEXES; i++)
{
// Close the Query
CloseQuery(&m_rghFilter[i], this);
}
// Free the Heap Cache
for (i=0; i<CC_HEAP_BUCKETS; i++)
{
// Locals
LPMEMORYTAG pTag;
LPVOID pNext;
LPVOID pCurrent=(LPVOID)m_rgpRecycle[i];
// While we have something to free
while(pCurrent)
{
// Set Tag
pTag = (LPMEMORYTAG)pCurrent;
// Debug
IF_DEBUG(m_cbHeapFree += pTag->cbSize);
// Save Next
pNext = pTag->pNext;
// Free Current
#ifdef USEHEAP
::HeapFree(m_hHeap, HEAP_NO_SERIALIZE, pCurrent);
#else
g_pMalloc->Free(pCurrent);
#endif
// Set Current
pCurrent = pNext;
}
// Null It
m_rgpRecycle[i] = NULL;
}
// Leaks ?
Assert(m_cbHeapAlloc == m_cbHeapFree);
// Release the Heap
if (m_hHeap)
{
// HeapDestroy
HeapDestroy(m_hHeap);
// Don't free again
m_hHeap = NULL;
}
// Reset Locals
m_pSchema = NULL;
// Release the mutex
ReleaseMutex(m_hMutex);
// Close the Table Mutex
CloseHandle(m_hMutex);
// Delete Crit Sect.
DeleteCriticalSection(&m_csHeap);
exit:
// Release the listen thread
ListenThreadRelease();
// Release Dll
DllRelease();
// Done
return;
}
//--------------------------------------------------------------------------
// CDatabase::AddRef
//--------------------------------------------------------------------------
STDMETHODIMP_(ULONG) CDatabase::AddRef(void)
{
// Trace
TraceCall("CDatabase::AddRef");
// AddRef the Extension...
if (m_pExtension && NULL == m_pUnkRelease)
{
// Keep Track of how many times I have addref'ed the extension
InterlockedIncrement(&m_cExtRefs);
// AddRef It
m_pExtension->AddRef();
}
// Increment My Ref Count
return InterlockedIncrement(&m_cRef);
}
//--------------------------------------------------------------------------
// CDatabase::Release
//--------------------------------------------------------------------------
STDMETHODIMP_(ULONG) CDatabase::Release(void)
{
// Trace
TraceCall("CDatabase::Release");
// Release the Extension...
if (m_pExtension && NULL == m_pUnkRelease && m_cExtRefs > 0)
{
// Keep Track of how many times I have addref'ed the extension
InterlockedDecrement(&m_cExtRefs);
// AddRef It
m_pExtension->Release();
}
// Do My Release
LONG cRef = InterlockedDecrement(&m_cRef);
// If zero, delete
if (0 == cRef)
delete this;
// Return Ref Count
return (ULONG)cRef;
}
//--------------------------------------------------------------------------
// CDatabase::QueryInterface
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::QueryInterface(REFIID riid, LPVOID *ppv)
{
// Locals
HRESULT hr=S_OK;
// Stack
TraceCall("CDatabase::QueryInterface");
// Find IID
if (IID_IUnknown == riid)
*ppv = (IUnknown *)(IDatabase *)this;
else if (IID_IDatabase == riid)
*ppv = (IDatabase *)this;
else if (IID_CDatabase == riid)
*ppv = (CDatabase *)this;
else
{
*ppv = NULL;
hr = TraceResult(E_NOINTERFACE);
goto exit;
}
// AddRef It
((IUnknown *)*ppv)->AddRef();
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::Open
//--------------------------------------------------------------------------
HRESULT CDatabase::Open(LPCWSTR pszFile, OPENDATABASEFLAGS dwFlags,
LPCTABLESCHEMA pSchema, IDatabaseExtension *pExtension)
{
// Locals
HRESULT hr=S_OK;
LPWSTR pszShare=NULL;
LPWSTR pszMutex=NULL;
LPWSTR pszFilePath=NULL;
BOOL fNewShare;
BOOL fNewFileMap;
BOOL fFileCreated;
BOOL fFileCorrupt = FALSE;
DWORD cbInitialSize;
DWORD cbMinFileSize;
DWORD cchFilePath;
LPCLIENTENTRY pClient;
// Trace
TraceCall("CDatabase::Open");
// Invalid Args
Assert(pszFile && pSchema);
// Already Open ?
if (m_hMutex)
return(TraceResult(DB_E_ALREADYOPEN));
// Get the Full Path
IF_FAILEXIT(hr = DBGetFullPath(pszFile, &pszFilePath, &cchFilePath));
// Failure
if (cchFilePath >= CCHMAX_DB_FILEPATH)
{
SafeMemFree(pszFilePath);
return(TraceResult(E_INVALIDARG));
}
// Don't use pszFile again
pszFile = NULL;
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(pszFilePath, L"_DirectDBMutex", &pszMutex));
// Create the Mutex
IF_NULLEXIT(m_hMutex = CreateMutexWrapW(NULL, FALSE, pszMutex));
// Wait for the Mutex
WaitForSingleObject(m_hMutex, INFINITE);
// Create the Heap
IF_NULLEXIT(m_hHeap = HeapCreate(0, 8096, 0));
// No Listen Window Yet ?
IF_FAILEXIT(hr = CreateListenThread());
// Save the Record Format, this should be global const data, so no need to duplicate it
m_pSchema = pSchema;
cbMinFileSize = sizeof(TABLEHEADER) + m_pSchema->cbUserData;
// Validate
IF_DEBUG(_DebugValidateRecordFormat());
// Allocate m_pStorage
IF_NULLEXIT(m_pStorage = (LPSTORAGEINFO)ZeroAllocate(sizeof(STORAGEINFO)));
// Exclusive
m_fExclusive = (ISFLAGSET(dwFlags, OPEN_DATABASE_EXCLUSEIVE) ? TRUE : FALSE);
// Open the File
IF_FAILEXIT(hr = DBOpenFile(pszFilePath, ISFLAGSET(dwFlags, OPEN_DATABASE_NOCREATE), m_fExclusive, &fFileCreated, &m_pStorage->hFile));
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(pszFilePath, L"_DirectDBShare", &pszShare));
// Open the file mapping
IF_FAILEXIT(hr = DBOpenFileMapping(INVALID_HANDLE_VALUE, pszShare, sizeof(SHAREDDATABASE), &fNewShare, &m_pStorage->hShare, (LPVOID *)&m_pShare));
// New Share
if (TRUE == fNewShare)
{
// Zero Out m_pShare
ZeroMemory(m_pShare, sizeof(SHAREDDATABASE));
// Copy the file name
StrCpyNW(m_pShare->szFile, pszFilePath, ARRAYSIZE(m_pShare->szFile));
// Fixup the Query Table Version
m_pShare->dwQueryVersion = 1;
}
// Too Many clients ?
if (m_pShare->cClients == CMAX_CLIENTS)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Readability
pClient = &m_pShare->rgClient[m_pShare->cClients];
// Initialize the Entry
ZeroMemory(pClient, sizeof(CLIENTENTRY));
// Get the listen window
GetListenWindow(&pClient->hwndListen);
// Register Myself
pClient->dwProcessId = m_dwProcessId;
pClient->pDB = (DWORD_PTR)this;
// Incrment Count
m_pShare->cClients++;
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(m_pShare->szFile, L"_DirectDBFileMap", &m_pStorage->pszMap));
// Get the file size
IF_FAILEXIT(hr = DBGetFileSize(m_pStorage->hFile, &cbInitialSize));
// If the file is too small to handle the header, then we either have a corrupt file
// that is way too corrupt to be saved, or we have an invalid file. Take some
// defensive measures to make sure we can continue
if (!fFileCreated && (cbInitialSize < cbMinFileSize))
{
fFileCorrupt = TRUE;
// If we can't reset or create, then must exit
if (ISFLAGSET(dwFlags, OPEN_DATABASE_NORESET) || ISFLAGSET(dwFlags, OPEN_DATABASE_NOCREATE))
{
IF_FAILEXIT(hr = HRESULT_FROM_WIN32(ERROR_FILE_CORRUPT));
}
// If we can reset, let's make sure the mapping file is the appropriate size
else
{
cbInitialSize = 0;
}
}
// Validate
Assert(fFileCreated ? 0 == cbInitialSize : TRUE);
// If zero, must be a new file or corrupt one(lets create with 1 byte header)
if (0 == cbInitialSize)
{
Assert(fFileCorrupt || fFileCreated);
// Initial Size
m_pStorage->cbFile = cbMinFileSize;
}
// Otherwise, Set m_pStorage->cbFile
else
m_pStorage->cbFile = cbInitialSize;
// Open the file mapping
IF_FAILEXIT(hr = DBOpenFileMapping(m_pStorage->hFile, m_pStorage->pszMap, m_pStorage->cbFile, &fNewFileMap, &m_pStorage->hMap, NULL));
// _InitializeFileViews
IF_FAILEXIT(hr = _InitializeFileViews());
// New File or corrupt?
if (fFileCreated || fFileCorrupt)
{
// Validate
Assert ((fFileCreated && fNewFileMap) || fFileCorrupt);
// Reset Table Header
IF_FAILEXIT(hr = _ResetTableHeader());
}
// Otherwise
else
{
// Adjust pHeader->faNextAllocate for previous versions...
if (0 == m_pHeader->faNextAllocate)
{
// The next storage grow address
m_pHeader->faNextAllocate = m_pStorage->cbFile;
}
// faNextAllocate is Invalid
else if (m_pHeader->faNextAllocate > m_pStorage->cbFile)
{
// Assert
AssertSz(FALSE, "m_pHeader->faNextAllocate is beyond the end of the file.");
// The next storage grow address
m_pHeader->faNextAllocate = m_pStorage->cbFile;
// Check for Corruption
m_pHeader->fCorruptCheck = FALSE;
}
}
// Validate File Versions and Signatures
IF_FAILEXIT(hr = _ValidateFileVersions(dwFlags));
// Reload query table
IF_FAILEXIT(hr = _BuildQueryTable());
// No Indexes, must need to initialize index info
if (0 == m_pHeader->cIndexes)
{
// Copy Primary Index Information...
CopyMemory(&m_pHeader->rgIndexInfo[IINDEX_PRIMARY], m_pSchema->pPrimaryIndex, sizeof(TABLEINDEX));
// We now have one index
m_pHeader->cIndexes = 1;
// Validate
Assert(IINDEX_PRIMARY == m_pHeader->rgiIndex[0] && 0 == m_pHeader->rgcRecords[IINDEX_PRIMARY] && 0 == m_pHeader->rgfaIndex[IINDEX_PRIMARY]);
}
// Otherwise, if definition of primary index has changed!!!
else if (S_FALSE == CompareTableIndexes(&m_pHeader->rgIndexInfo[IINDEX_PRIMARY], m_pSchema->pPrimaryIndex))
{
// Copy Primary Index Information...
CopyMemory(&m_pHeader->rgIndexInfo[IINDEX_PRIMARY], m_pSchema->pPrimaryIndex, sizeof(TABLEINDEX));
// Rebuild the Primary Index...
IF_FAILEXIT(hr = _RebuildIndex(IINDEX_PRIMARY));
}
// New Share
if (TRUE == fNewFileMap)
{
// Don't Free Transact list if transaction block size has changed
if (m_pHeader->wTransactSize == sizeof(TRANSACTIONBLOCK))
{
// Transaction Trail should be free
_CleanupTransactList();
}
// Reset Everything
m_pHeader->faTransactHead = m_pHeader->faTransactTail = m_pHeader->cTransacts = 0;
// Set Transaction Block Size
m_pHeader->wTransactSize = sizeof(TRANSACTIONBLOCK);
// Bad close ?
if (m_pHeader->cActiveThreads > 0)
{
// Increment Bad Close Count
m_pHeader->cBadCloses++;
// Reset Process Count
m_pHeader->cActiveThreads = 0;
}
}
// Otherwise, if the corrupt bit is set, run the repair code
if (TRUE == m_pHeader->fCorrupt || FALSE == m_pHeader->fCorruptCheck)
{
// Lets validate the tree
IF_FAILEXIT(hr = _CheckForCorruption());
// Better not be corrupt
Assert(FALSE == m_pHeader->fCorrupt);
// Checked for Corruption
m_pHeader->fCorruptCheck = TRUE;
}
// Initialize Database Extension
_InitializeExtension(dwFlags, pExtension);
#ifdef BACKGROUND_MONITOR
// If nomonitor is not set
if (!ISFLAGSET(dwFlags, OPEN_DATABASE_NOMONITOR))
{
// Keep on eye on me...
IF_FAILEXIT(hr = RegisterWithMonitor(this, &m_hMonitor));
}
#endif
// Increment Number of Processes
m_pHeader->cActiveThreads++;
exit:
// Release the Mutex
if (m_hMutex)
ReleaseMutex(m_hMutex);
// Cleanup
SafeMemFree(pszShare);
SafeMemFree(pszMutex);
SafeMemFree(pszFilePath);
// Done
return(hr);
}
#ifdef BACKGROUND_MONITOR
//--------------------------------------------------------------------------
// CDatabase::DoBackgroundMonitor
//--------------------------------------------------------------------------
HRESULT CDatabase::DoBackgroundMonitor(void)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
LPFILEVIEW pView;
LPFILEVIEW pNext;
BOOL fUnmapViews=TRUE;
// bobn, 7/8/99
// Occasionally, m_pSchema can be invalid due to a race condition in SMAPI
// We need to protect against that. We are too close to ship to
// find the race condition and re-architect the product to fix completely
// this corner case.
if (IsBadReadPtr(m_pSchema, sizeof(TABLESCHEMA)))
return(TraceResult(E_FAIL));
// No Mutex
if (NULL == m_hMutex)
return(TraceResult(E_FAIL));
// Leave Spin Lock
if (WAIT_OBJECT_0 != WaitForSingleObject(m_hMutex, 500))
return(S_OK);
// No Header ?
if (NULL == m_pHeader)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// No Storage
if (NULL == m_pStorage)
{
hr = TraceResult(E_FAIL);
goto exit;
}
#if 0
// Un-map file views ?
if (0 == m_pStorage->tcMonitor)
{
// I will unmap all views
fUnmapViews = FALSE;
}
#endif
// Always Flush the header
m_fDirty = TRUE;
if (0 == FlushViewOfFile(m_pHeader, sizeof(TABLEHEADER) + m_pSchema->cbUserData))
{
hr = TraceResult(DB_E_FLUSHVIEWOFFILE);
goto exit;
}
// Walk through prgView
for (i=0; i<m_pStorage->cAllocated; i++)
{
// Readability
pView = &m_pStorage->prgView[i];
// Is Mapped ?
if (pView->pbView)
{
// Flush the header
if (0 == FlushViewOfFile(pView->pbView, pView->cbView))
{
hr = TraceResult(DB_E_FLUSHVIEWOFFILE);
goto exit;
}
// Flush and UnMap the Views...
if (TRUE == fUnmapViews)
{
// Unmap
UnmapViewOfFile(pView->pbView);
// No view
pView->pbView = NULL;
// No view
pView->faView = pView->cbView = 0;
}
}
}
// Walk through pSpecial
pView = m_pStorage->pSpecial;
// While we have a Current
while (pView)
{
// Save pNext
pNext = pView->pNext;
// Is Mapped ?
if (pView->pbView)
{
// Flush the header
if (0 == FlushViewOfFile(pView->pbView, pView->cbView))
{
hr = TraceResult(DB_E_FLUSHVIEWOFFILE);
goto exit;
}
// Flush and UnMap the Views...
if (TRUE == fUnmapViews)
{
// Unmap
UnmapViewOfFile(pView->pbView);
// No view
pView->pbView = NULL;
// No view
pView->faView = pView->cbView = 0;
// Free pView
HeapFree(pView);
}
}
// Goto Next
pView = pNext;
}
// Reset Head
if (TRUE == fUnmapViews)
{
// No more special views
m_pStorage->pSpecial = NULL;
// No more special views
m_pStorage->cSpecial = 0;
// No Mapped Views
m_pStorage->cbMappedViews = 0;
// No Mapped Special Views
m_pStorage->cbMappedSpecial = 0;
}
// Save tcMonitor
m_pStorage->tcMonitor = GetTickCount();
exit:
// Release the Mutex
ReleaseMutex(m_hMutex);
// Done
return(hr);
}
#endif
//--------------------------------------------------------------------------
// CDatabase::_CloseFileViews
//--------------------------------------------------------------------------
HRESULT CDatabase::_CloseFileViews(BOOL fFlush)
{
// Locals
LPFILEVIEW pCurrent;
LPFILEVIEW pNext;
DWORD i;
// Trace
TraceCall("CDatabase::_CloseFileViews");
// Unmap the view of the header
UnmapViewOfFileWithFlush(fFlush, m_pHeader, sizeof(TABLEHEADER) + m_pSchema->cbUserData);
// Walk through prgView
for (i = 0; i < m_pStorage->cAllocated; i++)
{
// Readability
pCurrent = &m_pStorage->prgView[i];
// Unmap with possible flush
UnmapViewOfFileWithFlush(fFlush, pCurrent->pbView, pCurrent->cbView);
}
// Free prgView
SafeHeapFree(m_pStorage->prgView);
// No Views are mapped
m_pStorage->cbMappedViews = 0;
// Zero cAllocate
m_pStorage->cAllocated = 0;
// Walk through pSpecial
pCurrent = m_pStorage->pSpecial;
// While we have a Current
while (pCurrent)
{
// Save pNext
pNext = pCurrent->pNext;
// Unmap the view
UnmapViewOfFileWithFlush(fFlush, pCurrent->pbView, pCurrent->cbView);
// Free pCurrent
HeapFree(pCurrent);
// Goto Next
pCurrent = pNext;
}
// Reset Head
m_pStorage->pSpecial = NULL;
// No Special Mapped
m_pStorage->cbMappedSpecial = 0;
// No Special
m_pStorage->cSpecial = 0;
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_InitializeFileViews
//--------------------------------------------------------------------------
HRESULT CDatabase::_InitializeFileViews(void)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faView;
DWORD cbView;
// Trace
TraceCall("CDatabase::_InitializeFileViews");
// Validate State
Assert(NULL == m_pStorage->prgView && NULL == m_pStorage->pSpecial);
// Set cAllocated
m_pStorage->cAllocated = (m_pStorage->cbFile / CB_MAPPED_VIEW) + 1;
// Allocate prgView
IF_NULLEXIT(m_pStorage->prgView = (LPFILEVIEW)PHeapAllocate(HEAP_ZERO_MEMORY, sizeof(FILEVIEW) * m_pStorage->cAllocated));
// Set faView
faView = 0;
// Set cbView
cbView = (sizeof(TABLEHEADER) + m_pSchema->cbUserData);
// Map m_pHeader into its own view...
IF_FAILEXIT(hr = DBMapViewOfFile(m_pStorage->hMap, m_pStorage->cbFile, &faView, &cbView, (LPVOID *)&m_pHeader));
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_InitializeExtension
//--------------------------------------------------------------------------
HRESULT CDatabase::_InitializeExtension(OPENDATABASEFLAGS dwFlags,
IDatabaseExtension *pExtension)
{
// Locals
HRESULT hr=S_OK;
// Trace
TraceCall("CDatabase::_InitializeExtension");
// Extension Allowed
if (ISFLAGSET(dwFlags, OPEN_DATABASE_NOEXTENSION))
goto exit;
// Doesn't have an extension ?
if (FALSE == ISFLAGSET(m_pSchema->dwFlags, TSF_HASEXTENSION))
goto exit;
// Create the Extension Object
if (pExtension)
{
// Assume It
m_pExtension = pExtension;
// Can I add ref it ?
if (FALSE == ISFLAGSET(dwFlags, OPEN_DATABASE_NOADDREFEXT))
{
// Release It
IF_FAILEXIT(hr = m_pExtension->QueryInterface(IID_IUnknown, (LPVOID *)&m_pUnkRelease));
}
}
// Otherwise, CoCreate... the extension
else
{
// CoCreate the Extension Object
IF_FAILEXIT(hr = CoCreateInstance(*m_pSchema->pclsidExtension, NULL, CLSCTX_INPROC_SERVER, IID_IDatabaseExtension, (LPVOID *)&m_pExtension));
// Release It
IF_FAILEXIT(hr = m_pExtension->QueryInterface(IID_IUnknown, (LPVOID *)&m_pUnkRelease));
// Release m_pExtension
m_pExtension->Release();
}
// Initialize the Extension
m_pExtension->Initialize(this);
exit:
// Must have succeeded
Assert(SUCCEEDED(hr));
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetClientCount
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::GetClientCount(LPDWORD pcClients)
{
// Trace
TraceCall("CDatabase::GetClientCount");
// Multiple Clients ?
if (m_pShare)
*pcClients = m_pShare->cClients;
else
*pcClients = 0;
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::Lock
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::Lock(LPHLOCK phLock)
{
// Locals
HRESULT hr=S_OK;
BYTE fDecWaiting=FALSE;
// Trace
TraceCall("CDatabase::Lock");
// Initialize
*phLock = NULL;
// If Compacting...
if (TRUE == m_pShare->fCompacting)
{
// Increment Waiting for Lock
InterlockedIncrement(&m_pShare->cWaitingForLock);
// fDecWaiting
fDecWaiting = TRUE;
}
// Leave Spin Lock
WaitForSingleObject(m_hMutex, INFINITE);
// Decrement Waiting for lock ?
if (fDecWaiting)
{
// Increment Waiting for Lock
InterlockedDecrement(&m_pShare->cWaitingForLock);
}
// No Header ?
if (NULL == m_pHeader)
{
// Try to re-open the file
hr = DoInProcessInvoke(INVOKE_CREATEMAP);
// Failure
if (FAILED(hr))
{
// Leave Spin Lock
ReleaseMutex(m_hMutex);
// Trace
TraceResult(hr);
// Done
goto exit;
}
}
// Extension
if (m_pExtension)
{
// OnLock Extension...
m_pExtension->OnLock();
}
// Check for Corruption...
if (TRUE == m_pHeader->fCorrupt)
{
// Try to Repair Corruption
hr = _CheckForCorruption();
// Failure
if (FAILED(hr))
{
// Leave Spin Lock
ReleaseMutex(m_hMutex);
// Trace
TraceResult(hr);
// Done
goto exit;
}
}
// Need to reload quieries
if (m_dwQueryVersion != m_pShare->dwQueryVersion)
{
// Reload query table
IF_FAILEXIT(hr = _BuildQueryTable());
}
// Increment Queue Notify count
m_pShare->cNotifyLock++;
#ifdef BACKGROUND_MONITOR
// Reset tcMonitor
m_pStorage->tcMonitor = 0;
#endif
// Don't Unlock Again
*phLock = (HLOCK)m_hMutex;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::Unlock
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::Unlock(LPHLOCK phLock)
{
// Trace
TraceCall("CDatabase::Unlock");
// Not Null
if (*phLock)
{
// Extension
if (m_pExtension)
{
// OnUnlock Extension...
m_pExtension->OnUnlock();
}
// Unlock Notify
m_pShare->cNotifyLock--;
// If there are still refs, don't send notifications yet...
if (0 == m_pShare->cNotifyLock && FALSE == m_pHeader->fCorrupt)
{
// Dispatch Pending
_DispatchPendingNotifications();
}
// Validate phLock
Assert(*phLock == (HLOCK)m_hMutex);
// Leave Spin Lock
ReleaseMutex(m_hMutex);
// Don't Unlock Again
*phLock = NULL;
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_BuildQueryTable
//--------------------------------------------------------------------------
HRESULT CDatabase::_BuildQueryTable(void)
{
// Locals
HRESULT hr=S_OK;
ULONG i;
INDEXORDINAL iIndex;
LPBLOCKHEADER pBlock;
// Trace
TraceCall("CDatabase::_BuildQueryTable");
// Versions should be different
Assert(m_dwQueryVersion != m_pShare->dwQueryVersion);
// Collapse the Ordinal Array
for (i=0; i<m_pHeader->cIndexes; i++)
{
// Set iIndex
iIndex = m_pHeader->rgiIndex[i];
// Close the Current Filters
CloseQuery(&m_rghFilter[iIndex], this);
// Filter
if (m_pHeader->rgfaFilter[iIndex])
{
// Validate File Address
IF_FAILEXIT(hr = _GetBlock(BLOCK_STRING, m_pHeader->rgfaFilter[iIndex], (LPVOID *)&pBlock));
// Load the Query String
if (FAILED(ParseQuery(PSTRING(pBlock), m_pSchema, &m_rghFilter[iIndex], this)))
{
// Null
m_rghFilter[iIndex] = NULL;
}
}
}
// Save New Version
m_dwQueryVersion = m_pShare->dwQueryVersion;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_ResetTableHeader
//--------------------------------------------------------------------------
HRESULT CDatabase::_ResetTableHeader(void)
{
// Locals
HRESULT hr=S_OK;
// Trace
TraceCall("CDatabase::_ResetTableHeader");
// Zero out the Table Header + UserData
ZeroMemory(m_pHeader, sizeof(TABLEHEADER) + m_pSchema->cbUserData);
// Set the File Signature
m_pHeader->dwSignature = BTREE_SIGNATURE;
// Set the Major Version
m_pHeader->dwMajorVersion = BTREE_VERSION;
// Store the size of the user data
m_pHeader->cbUserData = m_pSchema->cbUserData;
// Set faNextAllocate
m_pHeader->faNextAllocate = sizeof(TABLEHEADER) + m_pSchema->cbUserData;
// Initialize ID Generator
m_pHeader->dwNextId = 1;
// No need to do the corruption check, its a new file...
m_pHeader->fCorruptCheck = TRUE;
// Store the clsidExtension
CopyMemory(&m_pHeader->clsidExtension, m_pSchema->pclsidExtension, sizeof(CLSID));
// Store the Version
m_pHeader->dwMinorVersion = m_pSchema->dwMinorVersion;
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_ValidateFileVersions
//--------------------------------------------------------------------------
HRESULT CDatabase::_ValidateFileVersions(OPENDATABASEFLAGS dwFlags)
{
// Locals
HRESULT hr=S_OK;
// Trace
TraceCall("CDatabase::_ValidateFileVersions");
// Signature better match
if (m_pHeader->dwSignature != BTREE_SIGNATURE)
{
hr = TraceResult(DB_E_INVALIDFILESIGNATURE);
goto exit;
}
// Validate the Major Version
if (m_pHeader->dwMajorVersion != BTREE_VERSION)
{
hr = TraceResult(DB_E_BADMAJORVERSION);
goto exit;
}
// Validate the Minor Version
if (m_pHeader->dwMinorVersion != m_pSchema->dwMinorVersion)
{
hr = TraceResult(DB_E_BADMINORVERSION);
goto exit;
}
// Validate the Minor Version
if (FALSE == IsEqualCLSID(m_pHeader->clsidExtension, *m_pSchema->pclsidExtension))
{
hr = TraceResult(DB_E_BADEXTENSIONCLSID);
goto exit;
}
exit:
// Can I Reset
if (FALSE == ISFLAGSET(dwFlags, OPEN_DATABASE_NORESET))
{
// Failed and reset if bad version ?
if (FAILED(hr) && ISFLAGSET(m_pSchema->dwFlags, TSF_RESETIFBADVERSION))
{
// Reset the Table Header
hr = _ResetTableHeader();
}
}
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::PHeapAllocate
//--------------------------------------------------------------------------
LPVOID CDatabase::PHeapAllocate(DWORD dwFlags, DWORD cbSize)
{
// Locals
LPMEMORYTAG pTag;
// Trace
TraceCall("CDatabase::PHeapAllocate");
// Increment the Size Enough to Store a Header
cbSize += sizeof(MEMORYTAG);
// Block is a too big to recycle ?
#ifdef HEAPCACHE
if (cbSize >= CB_MAX_HEAP_BUCKET)
{
#endif
// Thread Safety
EnterCriticalSection(&m_csHeap);
// Allocate the Block
#ifdef USEHEAP
LPVOID pBlock = HeapAlloc(m_hHeap, dwFlags | HEAP_NO_SERIALIZE, cbSize);
#else
LPVOID pBlock = ZeroAllocate(cbSize);
#endif
// Debug
IF_DEBUG(m_cbHeapAlloc += cbSize);
// Thread Safety
LeaveCriticalSection(&m_csHeap);
// Set pTag
pTag = (LPMEMORYTAG)pBlock;
#ifdef HEAPCACHE
}
// Otherwise
else
{
// Compute Free Block Bucket
WORD iBucket = ((WORD)(cbSize / CB_HEAP_BUCKET));
// Decrement iBucket ?
if (0 == (cbSize % CB_HEAP_BUCKET))
{
// Previous Bucket
iBucket--;
}
// Adjust cbBlock to fit completly into it's bucket
cbSize = ((iBucket + 1) * CB_HEAP_BUCKET);
// Thread Safety
EnterCriticalSection(&m_csHeap);
// Is there a block in this Bucket ?
if (m_rgpRecycle[iBucket])
{
// Use this block
pTag = (LPMEMORYTAG)m_rgpRecycle[iBucket];
// Validate Size
Assert(cbSize == pTag->cbSize);
// Fixup m_rgpRecycle
m_rgpRecycle[iBucket] = (LPBYTE)pTag->pNext;
// Zero
if (ISFLAGSET(dwFlags, HEAP_ZERO_MEMORY))
{
// Zero
ZeroMemory((LPBYTE)pTag, cbSize);
}
}
// Otherwise, allocate
else
{
// Allocate the Block
#ifdef USEHEAP
LPVOID pBlock = HeapAlloc(m_hHeap, dwFlags | HEAP_NO_SERIALIZE, cbSize);
#else
LPVOID pBlock = ZeroAllocate(cbSize);
#endif
// Debug
IF_DEBUG(m_cbHeapAlloc += cbSize);
// Set pTag
pTag = (LPMEMORYTAG)pBlock;
}
// Thread Safety
LeaveCriticalSection(&m_csHeap);
}
#endif
// No pTag
if (NULL == pTag)
return(NULL);
// Fixup the Block Size
pTag->cbSize = cbSize;
// Set Signature
pTag->dwSignature = MEMORY_GUARD_SIGNATURE;
// Done
return((LPBYTE)pTag + sizeof(MEMORYTAG));
}
//--------------------------------------------------------------------------
// CDatabase::HeapFree
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::HeapFree(LPVOID pBlock)
{
// Locals
LPMEMORYTAG pTag;
// Trace
TraceCall("CDatabase::HeapFree");
// No Buffer
if (NULL == pBlock)
return(S_OK);
// Set pTag
pTag = (LPMEMORYTAG)((LPBYTE)pBlock - sizeof(MEMORYTAG));
// Is Valid Block ?
Assert(pTag->dwSignature == MEMORY_GUARD_SIGNATURE);
// Block is a too big to recycle ?
#ifdef HEAPCACHE
if (pTag->cbSize >= CB_MAX_HEAP_BUCKET)
{
#endif
// Thread Safety
EnterCriticalSection(&m_csHeap);
// Debug
IF_DEBUG(m_cbHeapFree += pTag->cbSize);
// Allocate the Block
#ifdef USEHEAP
::HeapFree(m_hHeap, HEAP_NO_SERIALIZE, pTag);
#else
g_pMalloc->Free(pTag);
#endif
// Thread Safety
LeaveCriticalSection(&m_csHeap);
#ifdef HEAPCACHE
}
// Otherwise, cache It
else
{
// Compute Free Block Bucket
WORD iBucket = ((WORD)(pTag->cbSize / CB_HEAP_BUCKET)) - 1;
// Must be an integral size of a bucket
Assert((pTag->cbSize % CB_HEAP_BUCKET) == 0);
// Thread Safety
EnterCriticalSection(&m_csHeap);
// Set Next
pTag->pNext = m_rgpRecycle[iBucket];
// Set the Head
m_rgpRecycle[iBucket] = (LPBYTE)pTag;
// Thread Safety
LeaveCriticalSection(&m_csHeap);
}
#endif
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::GetIndexInfo
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::GetIndexInfo(INDEXORDINAL iIndex, LPSTR *ppszFilter,
LPTABLEINDEX pIndex)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
HLOCK hLock=NULL;
LPBLOCKHEADER pBlock;
// Trace
TraceCall("CDatabase::GetIndexInfo");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Collapse the Ordinal Array
for (i=0; i<m_pHeader->cIndexes; i++)
{
// Get iIndex
if (iIndex == m_pHeader->rgiIndex[i])
{
// Copy the Index Information
CopyMemory(pIndex, &m_pHeader->rgIndexInfo[iIndex], sizeof(TABLEINDEX));
// Get the Filters ?
if (ppszFilter && m_pHeader->rgfaFilter[iIndex])
{
// Corrupt
IF_FAILEXIT(hr = _GetBlock(BLOCK_STRING, m_pHeader->rgfaFilter[iIndex], (LPVOID *)&pBlock));
// Duplicate
IF_NULLEXIT(*ppszFilter = DuplicateStringA(PSTRING(pBlock)));
}
// Done
goto exit;
}
}
// Failure
hr = E_FAIL;
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::ModifyIndex
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::ModifyIndex(INDEXORDINAL iIndex, LPCSTR pszFilter,
LPCTABLEINDEX pIndex)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
HQUERY hFilter=NULL;
FILEADDRESS faFilter=0;
LPBLOCKHEADER pFilter=NULL;
BOOL fFound=FALSE;
DWORD i;
DWORD cb;
BOOL fVersionChange=FALSE;
// Trace
TraceCall("CDatabase::ModifyIndex");
// Invalid Args
if (IINDEX_PRIMARY == iIndex || iIndex > CMAX_INDEXES || NULL == pIndex || pIndex->cKeys > CMAX_KEYS)
return TraceResult(E_INVALIDARG);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Filter
if (pszFilter)
{
// Parse the Query
IF_FAILEXIT(hr = ParseQuery(pszFilter, m_pSchema, &hFilter, this));
// Initialize the String Block
cb = lstrlen(pszFilter) + 1;
// Try to Store the Query String
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_STRING, cb, (LPVOID *)&pFilter));
// Write the String
CopyMemory(PSTRING(pFilter), pszFilter, cb);
// Set faFilter
faFilter = pFilter->faBlock;
// Query Version Change
fVersionChange = TRUE;
}
// Free This Index
IF_FAILEXIT(hr = DeleteIndex(iIndex));
// Copy the Index Information
CopyMemory(&m_pHeader->rgIndexInfo[iIndex], pIndex, sizeof(TABLEINDEX));
// Filter
if (hFilter)
{
// Validate
Assert(NULL == m_rghFilter[iIndex] && 0 == m_pHeader->rgfaFilter[iIndex] && hFilter && faFilter);
// Store the hFilter
m_rghFilter[iIndex] = hFilter;
// Don't Free hFilter
hFilter = NULL;
// Store filter string address
m_pHeader->rgfaFilter[iIndex] = faFilter;
// Don't Free the Filter
faFilter = 0;
}
// Update Query Versions
if (fVersionChange)
{
// Update the Shared Query Version Count
m_pShare->dwQueryVersion++;
// I'm Up-to-date
m_dwQueryVersion = m_pShare->dwQueryVersion;
}
// Is iIndex already in rgiIndex ?
for (i=0; i<m_pHeader->cIndexes; i++)
{
// Is this it ?
if (iIndex == m_pHeader->rgiIndex[i])
{
// Its already in there
fFound = TRUE;
// Done
break;
}
}
// No Found
if (FALSE == fFound)
{
// Insert into Index Ordinal Array
m_pHeader->rgiIndex[m_pHeader->cIndexes] = iIndex;
// Increment Count
m_pHeader->cIndexes++;
}
// Rebuild the Index
IF_FAILEXIT(hr = _RebuildIndex(iIndex));
exit:
// Close Filters
CloseQuery(&hFilter, this);
// Free faFilter1
if (0 != faFilter)
{
// Free the block
SideAssert(SUCCEEDED(_FreeBlock(BLOCK_STRING, faFilter)));
}
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::DeleteIndex
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::DeleteIndex(INDEXORDINAL iIndex)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
BOOL fFound=FALSE;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::DeleteIndex");
// Invalid Args
if (IINDEX_PRIMARY == iIndex || iIndex > CMAX_INDEXES)
return TraceResult(E_INVALIDARG);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Collapse the Ordinal Array
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Is this the Index to delete ?
if (m_pHeader->rgiIndex[i] == iIndex)
{
// Found
fFound = TRUE;
// Collapse the Array
MoveMemory(&m_pHeader->rgiIndex[i], &m_pHeader->rgiIndex[i + 1], sizeof(INDEXORDINAL) * (m_pHeader->cIndexes - (i + 1)));
// Decrement Index Count
m_pHeader->cIndexes--;
// Done
break;
}
}
// Not Found
if (FALSE == fFound)
{
// No Filter and no Exception
Assert(0 == m_pHeader->rgfaFilter[iIndex]);
// No Filter Handle
Assert(NULL == m_rghFilter[iIndex]);
// No Record
Assert(0 == m_pHeader->rgcRecords[iIndex]);
// Done
goto exit;
}
// If this Index is Currently In Use...
if (m_pHeader->rgfaIndex[iIndex])
{
// Free This Index
_FreeIndex(m_pHeader->rgfaIndex[iIndex]);
// Null It Out
m_pHeader->rgfaIndex[iIndex] = 0;
// No Record
m_pHeader->rgcRecords[iIndex] = 0;
}
// Delete Filter
if (m_pHeader->rgfaFilter[iIndex])
{
// Free the Block
IF_FAILEXIT(hr = _FreeBlock(BLOCK_STRING, m_pHeader->rgfaFilter[iIndex]));
// Close the filter handle
CloseQuery(&m_rghFilter[iIndex], this);
// Set to NULL
m_pHeader->rgfaFilter[iIndex] = 0;
// Update the Shared Query Version Count
m_pShare->dwQueryVersion++;
}
// I'm Up-to-date
m_dwQueryVersion = m_pShare->dwQueryVersion;
// Handle Should be closed
Assert(NULL == m_rghFilter[iIndex]);
// Send Notifications ?
if (m_pShare->rgcIndexNotify[iIndex] > 0)
{
// Build the Update Notification Package
_LogTransaction(TRANSACTION_INDEX_DELETED, iIndex, NULL, 0, 0);
}
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GenerateId
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::GenerateId(LPDWORD pdwId)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::GenerateId");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Loop Until I create a valid Id ?
while (1)
{
// Increment next id
m_pHeader->dwNextId++;
// Invalid Id ?
if (0 == m_pHeader->dwNextId)
continue;
// In Invalid Range
if (m_pHeader->dwNextId >= RESERVED_ID_MIN && m_pHeader->dwNextId <= RESERVED_ID_MAX)
continue;
// Its Good
break;
}
// Set pdwId
*pdwId = m_pHeader->dwNextId;
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetFile
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::GetFile(LPWSTR *ppszFile)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::GetFile");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Dupp
IF_NULLEXIT(*ppszFile = DuplicateStringW(m_pShare->szFile));
exit:
// Unlock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_DispatchNotification
//--------------------------------------------------------------------------
HRESULT CDatabase::_DispatchNotification(HTRANSACTION hTransaction)
{
// Locals
HRESULT hr=S_OK;
DWORD iClient;
LPCLIENTENTRY pClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
// Trace
TraceCall("CDatabase::_DispatchNotification");
// Walk through the List
for (iClient=0; iClient<m_pShare->cClients; iClient++)
{
// De-reference the client
pClient = &m_pShare->rgClient[iClient];
// Loop through cNotify
for (iRecipient=0; iRecipient<pClient->cRecipients; iRecipient++)
{
// De-Ref pEntry
pRecipient = &pClient->rgRecipient[iRecipient];
// If the Recipient isn't suspending...
if (FALSE == pRecipient->fSuspended)
{
// Should have a Thunking Window
Assert(pRecipient->hwndNotify && IsWindow(pRecipient->hwndNotify));
// Post the Notification
if (0 == PostMessage(pRecipient->hwndNotify, WM_ONTRANSACTION, (WPARAM)pRecipient->dwCookie, (LPARAM)hTransaction))
{
hr = TraceResult(E_FAIL);
goto exit;
}
}
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::DoInProcessInvoke
//--------------------------------------------------------------------------
HRESULT CDatabase::DoInProcessInvoke(INVOKETYPE tyInvoke)
{
// Locals
HRESULT hr=S_OK;
BOOL fNew;
// Trace
TraceCall("CDatabase::DoInProcessNotify");
// INVOKE_RELEASEMAP
if (INVOKE_RELEASEMAP == tyInvoke)
{
// Validate
_CloseFileViews(FALSE);
// Close the File
SafeCloseHandle(m_pStorage->hMap);
}
// INVOKE_CREATEMAP
else if (INVOKE_CREATEMAP == tyInvoke)
{
// Validation
Assert(NULL == m_pStorage->hMap);
// Get the file size
IF_FAILEXIT(hr = DBGetFileSize(m_pStorage->hFile, &m_pStorage->cbFile));
// Open the file mapping
IF_FAILEXIT(hr = DBOpenFileMapping(m_pStorage->hFile, m_pStorage->pszMap, m_pStorage->cbFile, &fNew, &m_pStorage->hMap, NULL));
// Initialize File Views
IF_FAILEXIT(hr = _InitializeFileViews());
}
// INVOKE_CLOSEFILE
else if (INVOKE_CLOSEFILE == tyInvoke)
{
// Validate
_CloseFileViews(TRUE);
// Close the File
if(m_pStorage->hFile /*&& m_fDirty*/)
{
FILETIME systime;
GetSystemTimeAsFileTime(&systime);
SetFileTime(m_pStorage->hFile, NULL, &systime, &systime);
}
SafeCloseHandle(m_pStorage->hMap);
// Close the file
SafeCloseHandle(m_pStorage->hFile);
}
// INVOKE_OPENFILE
else if (INVOKE_OPENFILE == tyInvoke || INVOKE_OPENMOVEDFILE == tyInvoke)
{
// Validation
Assert(NULL == m_pStorage->hFile && NULL == m_pStorage->hMap);
// Open Moved File ?
if (INVOKE_OPENMOVEDFILE == tyInvoke)
{
// _HandleOpenMovedFile
IF_FAILEXIT(hr = _HandleOpenMovedFile());
}
// Open the File
IF_FAILEXIT(hr = DBOpenFile(m_pShare->szFile, FALSE, m_fExclusive, &fNew, &m_pStorage->hFile));
// Better not be new
Assert(FALSE == fNew);
// Get the file size
IF_FAILEXIT(hr = DBGetFileSize(m_pStorage->hFile, &m_pStorage->cbFile));
// Open the file mapping
IF_FAILEXIT(hr = DBOpenFileMapping(m_pStorage->hFile, m_pStorage->pszMap, m_pStorage->cbFile, &fNew, &m_pStorage->hMap, NULL));
// Initialize File Views
IF_FAILEXIT(hr = _InitializeFileViews());
}
// Uhoh
else
AssertSz(FALSE, "Invalid invoke type passed into CDatabase::DoInProcessInvoke");
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_HandleOpenMovedFile
//--------------------------------------------------------------------------
HRESULT CDatabase::_HandleOpenMovedFile(void)
{
// Locals
HRESULT hr=S_OK;
LPWSTR pszMutex=NULL;
LPWSTR pszShare=NULL;
BOOL fNewShare;
WCHAR szFile[CCHMAX_DB_FILEPATH];
// Trace
TraceCall("CDatabase::_HandleOpenMovedFile");
// Save New File Path
StrCpyNW(szFile, m_pShare->szFile, ARRAYSIZE(szFile));
// Free pszMap
SafeMemFree(m_pStorage->pszMap);
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(szFile, L"_DirectDBFileMap", &m_pStorage->pszMap));
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(szFile, L"_DirectDBMutex", &pszMutex));
// Close the current mutex
SafeCloseHandle(m_hMutex);
// Create the Mutex
IF_NULLEXIT(m_hMutex = CreateMutexWrapW(NULL, FALSE, pszMutex));
// If not in move file
if (FALSE == m_fInMoveFile)
{
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(szFile, L"_DirectDBShare", &pszShare));
// Unmap the view of the memory mapped file
SafeUnmapViewOfFile(m_pShare);
// Unmap the view of the memory mapped file
SafeCloseHandle(m_pStorage->hShare);
// Open the file mapping
IF_FAILEXIT(hr = DBOpenFileMapping(INVALID_HANDLE_VALUE, pszShare, sizeof(SHAREDDATABASE), &fNewShare, &m_pStorage->hShare, (LPVOID *)&m_pShare));
// Better not be new
Assert(!fNewShare);
}
else
Assert(StrCmpW(szFile, m_pShare->szFile) == 0);
exit:
// Cleanup
SafeMemFree(pszMutex);
SafeMemFree(pszShare);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_DispatchInvoke
//--------------------------------------------------------------------------
HRESULT CDatabase::_DispatchInvoke(INVOKETYPE tyInvoke)
{
// Locals
HRESULT hr=S_OK;
DWORD iClient=0;
LPCLIENTENTRY pClient;
DWORD_PTR dwResult;
DWORD dwThreadId=GetCurrentThreadId();
INVOKEPACKAGE Package;
COPYDATASTRUCT CopyData;
// Trace
TraceCall("CDatabase::_DispatchInvoke");
// Set Invoke Type
Package.tyInvoke = tyInvoke;
// Walk through the List
while (iClient < m_pShare->cClients)
{
// Readability
pClient = &m_pShare->rgClient[iClient++];
// Better have one
Package.pDB = pClient->pDB;
// Is this entry in my process ?
if (m_dwProcessId == pClient->dwProcessId)
{
// Do In Process Notification
CDatabase *pDB = (CDatabase *)pClient->pDB;
// Do It
IF_FAILEXIT(hr = pDB->DoInProcessInvoke(tyInvoke));
}
// Otherwise, just process the package
else
{
// If the listener is good
if (pClient->hwndListen && IsWindow(pClient->hwndListen))
{
// Initialize copy data struct
CopyData.dwData = 0;
// Store the Size of the Package
CopyData.cbData = sizeof(INVOKEPACKAGE);
// Store the Package
CopyData.lpData = &Package;
// Send It
if (0 == SendMessageTimeout(pClient->hwndListen, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&CopyData, SMTO_ABORTIFHUNG, 5000, &dwResult))
{
// Remove this client from the list
SideAssert(SUCCEEDED(_RemoveClientFromArray(pClient->dwProcessId, pClient->pDB)));
// Decrement iClient
iClient--;
}
}
// Remove this client
else
{
// Remove this client from the list
SideAssert(SUCCEEDED(_RemoveClientFromArray(pClient->dwProcessId, pClient->pDB)));
// Decrement iClient
iClient--;
}
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_RemoveClientFromArray
//--------------------------------------------------------------------------
HRESULT CDatabase::_RemoveClientFromArray(DWORD dwProcessId,
DWORD_PTR dwDB)
{
// Locals
HRESULT hr=S_OK;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
DWORD cClients=0;
DWORD iClient;
LPCLIENTENTRY pClient;
// Trace
TraceCall("CDatabase::_RemoveClientFromArray");
// Initialize i
iClient = 0;
// Find this Client
IF_FAILEXIT(hr = _FindClient(dwProcessId, dwDB, &iClient, &pClient));
// Release Registered Notification Objects
for (iRecipient=0; iRecipient<pClient->cRecipients; iRecipient++)
{
// Readability
pRecipient = &pClient->rgRecipient[iRecipient];
// Same Process ?
if (dwProcessId == m_dwProcessId)
{
// Remove and messages from the notificaton queue
_CloseNotificationWindow(pRecipient);
// Release ?
if (TRUE == pRecipient->fRelease)
{
// Cast pNotify
IDatabaseNotify *pNotify = (IDatabaseNotify *)pRecipient->pNotify;
// Cast to pRecipient
pNotify->Release();
}
}
// If Not Suspended
if (FALSE == pRecipient->fSuspended)
{
// _AdjustNotifyCounts
_AdjustNotifyCounts(pRecipient, -1);
}
}
// Remove MySelf
MoveMemory(&m_pShare->rgClient[iClient], &m_pShare->rgClient[iClient + 1], sizeof(CLIENTENTRY) * (m_pShare->cClients - (iClient + 1)));
// Decrement Client Count
m_pShare->cClients--;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CloseNotificationWindow
//--------------------------------------------------------------------------
HRESULT CDatabase::_CloseNotificationWindow(LPNOTIFYRECIPIENT pRecipient)
{
// Trace
TraceCall("CDatabase::_CloseNotificationWindow");
// Kill the Window
DestroyWindow(pRecipient->hwndNotify);
// Null it Count
pRecipient->hwndNotify = NULL;
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_FindClient
//--------------------------------------------------------------------------
HRESULT CDatabase::_FindClient(DWORD dwProcessId, DWORD_PTR dwDB,
LPDWORD piClient, LPCLIENTENTRY *ppClient)
{
// Locals
HRESULT hr=S_OK;
LPCLIENTENTRY pClient;
DWORD iClient;
// Trace
TraceCall("CDatabase::_FindThisClient");
// Find myself in the client list
for (iClient=0; iClient<m_pShare->cClients; iClient++)
{
// Readability
pClient = &m_pShare->rgClient[iClient];
// Is this me ?
if (dwProcessId == pClient->dwProcessId && dwDB == pClient->pDB)
{
*piClient = iClient;
*ppClient = pClient;
goto exit;
}
}
// Not Found
hr = TraceResult(DB_E_NOTFOUND);
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FindNotifyRecipient
//--------------------------------------------------------------------------
HRESULT CDatabase::_FindNotifyRecipient(DWORD iClient, IDatabaseNotify *pNotify,
LPDWORD piRecipient, LPNOTIFYRECIPIENT *ppRecipient)
{
// Locals
HRESULT hr=S_OK;
LPCLIENTENTRY pClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
// Trace
TraceCall("CDatabase::_FindNotifyRecipient");
// Readability
pClient = &m_pShare->rgClient[iClient];
// Walk through client's registered notification entries
for (iRecipient = 0; iRecipient < m_pShare->rgClient[iClient].cRecipients; iRecipient++)
{
// Readability
pRecipient = &pClient->rgRecipient[iRecipient];
// Is this me ?
if ((DWORD_PTR)pNotify == pRecipient->pNotify)
{
// This is It
*piRecipient = iRecipient;
*ppRecipient = pRecipient;
goto exit;
}
}
// Not Found
hr = DB_E_NOTFOUND;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_DispatchPendingNotifications
//--------------------------------------------------------------------------
HRESULT CDatabase::_DispatchPendingNotifications(void)
{
// Are there pending notifications
if (m_pShare->faTransactLockHead)
{
// Dispatch Invoke
_DispatchNotification((HTRANSACTION)IntToPtr(m_pShare->faTransactLockHead));
// Null It Out
m_pShare->faTransactLockTail = m_pShare->faTransactLockHead = 0;
}
// Otherwise, validate
else
{
// Tail must be Null
Assert(0 == m_pShare->faTransactLockTail);
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::DispatchNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::DispatchNotify(IDatabaseNotify *pNotify)
{
// Locals
HRESULT hr=S_OK;
LPCLIENTENTRY pClient;
DWORD iClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
HLOCK hLock=NULL;
MSG msg;
// Trace
TraceCall("CDatabase::DispatchNotify");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Find this Client
IF_FAILEXIT(hr = _FindClient(m_dwProcessId, (DWORD_PTR)this, &iClient, &pClient));
// Find this recipient
IF_FAILEXIT(hr = _FindNotifyRecipient(iClient, pNotify, &iRecipient, &pRecipient));
// Need to dish out pending notifications....
_DispatchPendingNotifications();
// Processing the Pending Notifications for this recipient...
if (pRecipient->dwThreadId != GetCurrentThreadId())
{
Assert(FALSE);
hr = TraceResult(DB_E_WRONGTHREAD);
goto exit;
}
// Pump Messages
while (PeekMessage(&msg, pRecipient->hwndNotify, WM_ONTRANSACTION, WM_ONTRANSACTION, PM_REMOVE))
{
// Translate the Message
TranslateMessage(&msg);
// Dispatch the Message
DispatchMessage(&msg);
}
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::SuspendNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::SuspendNotify(IDatabaseNotify *pNotify)
{
// Locals
HRESULT hr=S_OK;
LPCLIENTENTRY pClient;
DWORD iClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
HLOCK hLock=NULL;
MSG msg;
// Trace
TraceCall("CDatabase::SuspendNotify");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Find this Client
IF_FAILEXIT(hr = _FindClient(m_dwProcessId, (DWORD_PTR)this, &iClient, &pClient));
// Find this recipient
IF_FAILEXIT(hr = _FindNotifyRecipient(iClient, pNotify, &iRecipient, &pRecipient));
// If Not Suspended yet
if (pRecipient->fSuspended)
goto exit;
// Need to dish out pending notifications....
_DispatchPendingNotifications();
// Processing the Pending Notifications for this recipient...
if (pRecipient->dwThreadId == GetCurrentThreadId())
{
// Pump Messages
while (PeekMessage(&msg, pRecipient->hwndNotify, WM_ONTRANSACTION, WM_ONTRANSACTION, PM_REMOVE))
{
// Translate the Message
TranslateMessage(&msg);
// Dispatch the Message
DispatchMessage(&msg);
}
}
// Otherwise, can't pump out pending notifications...
else
Assert(FALSE);
// Set Suspended
pRecipient->fSuspended = TRUE;
// Adjust Notify Counts
_AdjustNotifyCounts(pRecipient, -1);
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::ResumeNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::ResumeNotify(IDatabaseNotify *pNotify)
{
// Locals
HRESULT hr=S_OK;
LPCLIENTENTRY pClient;
DWORD iClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::ResumeNotify");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Find this Client
IF_FAILEXIT(hr = _FindClient(m_dwProcessId, (DWORD_PTR)this, &iClient, &pClient));
// Find this recipient
IF_FAILEXIT(hr = _FindNotifyRecipient(iClient, pNotify, &iRecipient, &pRecipient));
// If Not Suspended yet
if (FALSE == pRecipient->fSuspended)
goto exit;
// Need to dish out pending notifications....
_DispatchPendingNotifications();
// Remove fSuspended
pRecipient->fSuspended = FALSE;
// Adjust Notify Counts
_AdjustNotifyCounts(pRecipient, 1);
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_AdjustNotifyCounts
//--------------------------------------------------------------------------
HRESULT CDatabase::_AdjustNotifyCounts(LPNOTIFYRECIPIENT pRecipient,
LONG lChange)
{
// Trace
TraceCall("CDatabase::_AdjustNotifyCounts");
// Ordinals Only
if (pRecipient->fOrdinalsOnly)
{
// Validate the Count
Assert((LONG)(m_pShare->cNotifyOrdinalsOnly + lChange) >= 0);
// Update Ordinals Only Count
m_pShare->cNotifyOrdinalsOnly += lChange;
}
// Otherwise, update notify with data count
else
{
// Validate the Count
Assert((LONG)(m_pShare->cNotifyWithData + lChange) >= 0);
// Update
m_pShare->cNotifyWithData += lChange;
}
// Validate the Count
Assert((LONG)(m_pShare->cNotify + lChange) >= 0);
// Update Total cNotify
m_pShare->cNotify += lChange;
// Validate the Count
Assert((LONG)(m_pShare->rgcIndexNotify[pRecipient->iIndex] + lChange) >= 0);
// Decrement Number of Recipients for Index
m_pShare->rgcIndexNotify[pRecipient->iIndex] += lChange;
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::UnregisterNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::UnregisterNotify(IDatabaseNotify *pNotify)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
LPCLIENTENTRY pClient;
DWORD iClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
// Trace
TraceCall("CDatabase::UnregisterNotify");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Find this Client
IF_FAILEXIT(hr = _FindClient(m_dwProcessId, (DWORD_PTR)this, &iClient, &pClient));
// Find this recipient
hr = _FindNotifyRecipient(iClient, pNotify, &iRecipient, &pRecipient);
if (FAILED(hr))
{
hr = S_OK;
goto exit;
}
// Remove and messages from the notificaton queue
_CloseNotificationWindow(pRecipient);
// Release ?
if (TRUE == pRecipient->fRelease)
{
// Cast to pRecipient
pNotify->Release();
}
// If Not Suspended
if (FALSE == pRecipient->fSuspended)
{
// _AdjustNotifyCounts
_AdjustNotifyCounts(pRecipient, -1);
}
// Remove MySelf
MoveMemory(&pClient->rgRecipient[iRecipient], &pClient->rgRecipient[iRecipient + 1], sizeof(NOTIFYRECIPIENT) * (pClient->cRecipients - (iRecipient + 1)));
// Decrement Client Count
pClient->cRecipients--;
exit:
// Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::RegisterNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::RegisterNotify(INDEXORDINAL iIndex,
REGISTERNOTIFYFLAGS dwFlags, DWORD_PTR dwCookie,
IDatabaseNotify *pNotify)
{
// Locals
HRESULT hr=S_OK;
LPCLIENTENTRY pClient;
DWORD iClient;
DWORD iRecipient;
LPNOTIFYRECIPIENT pRecipient;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::RegisterNotify");
// Invalid Args
if (NULL == pNotify || iIndex > CMAX_INDEXES)
return TraceResult(E_INVALIDARG);
// If Deconstructing, just return
if (m_fDeconstruct)
return(S_OK);
// Thread Safety
IF_FAILEXIT(hr = Lock(&hLock));
// Find this Client
IF_FAILEXIT(hr = _FindClient(m_dwProcessId, (DWORD_PTR)this, &iClient, &pClient));
// See if this client is already registered...
if (SUCCEEDED(_FindNotifyRecipient(iClient, pNotify, &iRecipient, &pRecipient)))
{
hr = TraceResult(DB_E_ALREADYREGISTERED);
goto exit;
}
// Need to dish out pending notifications....
_DispatchPendingNotifications();
// Room for one more
if (pClient->cRecipients + 1 >= CMAX_RECIPIENTS)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Readability
pRecipient = &pClient->rgRecipient[pClient->cRecipients];
// Store the ThreadId
pRecipient->dwThreadId = GetCurrentThreadId();
// Save the Cookie
pRecipient->dwCookie = dwCookie;
// Get a Thunking Window for that thread
IF_FAILEXIT(hr = CreateNotifyWindow(this, pNotify, &pRecipient->hwndNotify));
// Only addref if the client wants me to
if (!ISFLAGSET(dwFlags, REGISTER_NOTIFY_NOADDREF))
{
// AddRef the notification object
pNotify->AddRef();
// Release it
pRecipient->fRelease = TRUE;
}
// Register It
pRecipient->pNotify = (DWORD_PTR)pNotify;
// Save the Index that they are intereseted in
pRecipient->iIndex = iIndex;
// Increment Notify Count
pClient->cRecipients++;
// Ordinals Only
pRecipient->fOrdinalsOnly = (ISFLAGSET(dwFlags, REGISTER_NOTIFY_ORDINALSONLY) ? TRUE : FALSE);
// _AdjustNotifyCounts
_AdjustNotifyCounts(pRecipient, 1);
exit:
// Thread Safety
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_SetStorageSize
//--------------------------------------------------------------------------
HRESULT CDatabase::_SetStorageSize(DWORD cbSize)
{
// Locals
HRESULT hr=S_OK;
HRESULT hrCreate;
// Trace
TraceCall("CDatabase::_SetStorageSize");
// Only if sizes are different
if (cbSize == m_pStorage->cbFile)
return(S_OK);
// Do It
_DispatchInvoke(INVOKE_RELEASEMAP);
// Set the File Pointer
if (0xFFFFFFFF == SetFilePointer(m_pStorage->hFile, cbSize, NULL, FILE_BEGIN))
{
hr = TraceResult(DB_E_SETFILEPOINTER);
goto exit;
}
// Set End of file
if (0 == SetEndOfFile(m_pStorage->hFile))
{
// Get LastError
DWORD dwLastError = GetLastError();
// Access Denied ?
if (ERROR_ACCESS_DENIED == dwLastError)
{
hr = TraceResult(DB_E_ACCESSDENIED);
goto exit;
}
// Otherwise, assume disk is full
else
{
hr = TraceResult(DB_E_DISKFULL);
goto exit;
}
}
exit:
// Do It
hrCreate = _DispatchInvoke(INVOKE_CREATEMAP);
// Done
return(SUCCEEDED(hr) ? hrCreate : hr);
}
//--------------------------------------------------------------------------
// CDatabase::SetSize
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::SetSize(DWORD cbSize)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::SetSize");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Size can only be larger than my current size
if (cbSize < m_pStorage->cbFile)
goto exit;
// If the size of the file is currently zero...
IF_FAILEXIT(hr = _SetStorageSize(cbSize));
exit:
// Invalid Args
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_AllocatePage
//--------------------------------------------------------------------------
HRESULT CDatabase::_AllocatePage(DWORD cbPage, LPFILEADDRESS pfaAddress)
{
// Locals
HRESULT hr=S_OK;
// Trace
TraceCall("CDatabase::_AllocatePage");
// Quick Validation
Assert(m_pHeader->faNextAllocate && m_pHeader->faNextAllocate <= m_pStorage->cbFile);
// Need to grow the file ?
if (m_pStorage->cbFile - m_pHeader->faNextAllocate < cbPage)
{
// Compute cbNeeded
DWORD cbNeeded = cbPage - (m_pStorage->cbFile - m_pHeader->faNextAllocate);
// Grow in at least 64k chunks.
cbNeeded = max(cbNeeded, 65536);
// If the size of the file is currently zero...
IF_FAILEXIT(hr = _SetStorageSize(m_pStorage->cbFile + cbNeeded));
}
// Return this address
*pfaAddress = m_pHeader->faNextAllocate;
// Adjust faNextAllocate
m_pHeader->faNextAllocate += cbPage;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_MarkBlock
//--------------------------------------------------------------------------
HRESULT CDatabase::_MarkBlock(BLOCKTYPE tyBlock, FILEADDRESS faBlock,
DWORD cbBlock, LPVOID *ppvBlock)
{
// Locals
HRESULT hr=S_OK;
MARKBLOCK Mark;
LPBLOCKHEADER pBlock;
// Trace
TraceCall("CDatabase::_MarkBlock");
// Validate
Assert(cbBlock >= g_rgcbBlockSize[tyBlock]);
// Set Mark
Mark.cbBlock = cbBlock;
// De-Ref the Header
IF_FAILEXIT(hr = _GetBlock(tyBlock, faBlock, (LPVOID *)&pBlock, &Mark));
// Zero the Header
ZeroBlock(pBlock, g_rgcbBlockSize[tyBlock]);
// Return ppvBlock
if (ppvBlock)
*ppvBlock = (LPVOID)pBlock;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_AllocateFromPage
//--------------------------------------------------------------------------
HRESULT CDatabase::_AllocateFromPage(BLOCKTYPE tyBlock, LPALLOCATEPAGE pPage,
DWORD cbPage, DWORD cbBlock, LPVOID *ppvBlock)
{
// Locals
HRESULT hr=S_OK;
DWORD cbLeft;
FILEADDRESS faBlock;
DWORD iBucket;
// Trace
TraceCall("CDatabase::_AllocateFromPage");
// Is Page Valid ?
if (pPage->faPage + pPage->cbPage > m_pStorage->cbFile)
{
// Kill the page
ZeroMemory(pPage, sizeof(ALLOCATEPAGE));
}
// Otherwise
else
{
// Compute cbLeft
cbLeft = pPage->cbPage - pPage->cbUsed;
}
// Requesting a large block
if (cbBlock > cbPage || (cbLeft > 0 && cbLeft < cbBlock && cbLeft >= CB_MAX_FREE_BUCKET))
{
// Allocate space in the file
IF_FAILEXIT(hr = _AllocatePage(cbBlock, &faBlock));
// Mark the block
IF_FAILEXIT(hr = _MarkBlock(tyBlock, faBlock, cbBlock, ppvBlock));
}
// Invalid Page ?
else
{
// Block is too small...
if (cbLeft > 0 && cbLeft < cbBlock)
{
// Must be a BLOCK_RECORD
Assert(BLOCK_STREAM != tyBlock && BLOCK_CHAIN != tyBlock);
// Better fit into block
Assert(cbLeft <= CB_MAX_FREE_BUCKET && cbLeft >= CB_MIN_FREE_BUCKET && (cbLeft % 4) == 0);
// Mark the block
IF_FAILEXIT(hr = _MarkBlock(BLOCK_ENDOFPAGE, (pPage->faPage + pPage->cbUsed), cbLeft, NULL));
// Increment cbAllocated
m_pHeader->cbAllocated += cbLeft;
// Increment
m_pHeader->rgcbAllocated[BLOCK_ENDOFPAGE] += cbLeft;
// Lets Free This block
IF_FAILEXIT(hr = _FreeBlock(BLOCK_ENDOFPAGE, (pPage->faPage + pPage->cbUsed)));
// Nothgin Left
cbLeft = 0;
}
// Use the entire page
else if (cbLeft != cbBlock && cbLeft - cbBlock < CB_MIN_FREE_BUCKET)
{
// Must be a BLOCK_RECORD
Assert(BLOCK_STREAM != tyBlock && BLOCK_CHAIN != tyBlock);
// Adjust cbBlock
cbBlock += (cbLeft - cbBlock);
}
// Need to allocate a page
if (0 == pPage->faPage || 0 == cbLeft)
{
// Kill the page
ZeroMemory(pPage, sizeof(ALLOCATEPAGE));
// Allocate space in the file
IF_FAILEXIT(hr = _AllocatePage(cbPage, &pPage->faPage));
// Set cbChainPageLeft
pPage->cbPage = cbPage;
}
// Mark the block
IF_FAILEXIT(hr = _MarkBlock(tyBlock, (pPage->faPage + pPage->cbUsed), cbBlock, ppvBlock));
// Set Next Allocation
pPage->cbUsed += cbBlock;
// Validate
Assert(pPage->cbUsed <= pPage->cbPage);
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_SetCorrupt
//--------------------------------------------------------------------------
HRESULT CDatabase::_SetCorrupt(BOOL fGoCorrupt, INT nLine,
CORRUPTREASON tyReason, BLOCKTYPE tyBlock, FILEADDRESS faExpected,
FILEADDRESS faActual, DWORD cbBlock)
{
// Trace
TraceCall("CDatabase::_SetCorrupt");
// Go Corrupt
if (fGoCorrupt)
{
// Store it in the header
m_pHeader->fCorrupt = TRUE;
}
// Done - This is always return to get the calling operation to abort
return(DB_E_CORRUPT);
}
//--------------------------------------------------------------------------
// CDatabase::_AllocateSpecialView
//--------------------------------------------------------------------------
HRESULT CDatabase::_AllocateSpecialView(FILEADDRESS faView,
DWORD cbView, LPFILEVIEW *ppSpecial)
{
// Locals
HRESULT hr=S_OK;
LPFILEVIEW pView=NULL;
// Trace
TraceCall("CDatabase::_AllocateSpecialView");
// Try to find existing special view where faView / cbView fits...
for (pView = m_pStorage->pSpecial; pView != NULL; pView = pView->pNext)
{
// Fit into this view ?
if (faView >= pView->faView && faView + cbView <= pView->faView + pView->cbView)
{
// This is good...
*ppSpecial = pView;
// Don't Freep
pView = NULL;
// Done
goto exit;
}
}
// Create a Special View
IF_NULLEXIT(pView = (LPFILEVIEW)PHeapAllocate(0, sizeof(FILEVIEW)));
// Set faView
pView->faView = faView;
// Set cbView
pView->cbView = cbView;
// Map the View
IF_FAILEXIT(hr = DBMapViewOfFile(m_pStorage->hMap, m_pStorage->cbFile, &pView->faView, &pView->cbView, (LPVOID *)&pView->pbView));
// Increment Statistic
m_pStorage->cSpecial++;
// Increment cbMappedSpecial
m_pStorage->cbMappedSpecial += pView->cbView;
// Link pView into Special List
pView->pNext = m_pStorage->pSpecial;
// Set pSpecial
m_pStorage->pSpecial = pView;
// Set Return
*ppSpecial = pView;
// Don't Free It
pView = NULL;
exit:
// Cleanup
SafeHeapFree(pView);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_GetBlock
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetBlock(BLOCKTYPE tyExpected, FILEADDRESS faBlock,
LPVOID *ppvBlock, LPMARKBLOCK pMark /* =NULL */, BOOL fGoCorrupt /* TRUE */)
{
// Locals
HRESULT hr=S_OK;
DWORD iViewStart;
DWORD iViewEnd;
LPFILEVIEW pView;
DWORD cbBlock;
LPBLOCKHEADER pBlock;
LPFILEVIEW pSpecial=NULL;
// Trace
TraceCall("CDatabase::_CheckBlock");
// Invalid Args
IxpAssert(faBlock > 0 && ppvBlock);
// Storage is Setup ?
IxpAssert(m_pStorage->hMap && m_pStorage->prgView);
// faBlock is Out-of-Range
if (faBlock + sizeof(BLOCKHEADER) >= m_pStorage->cbFile)
{
hr = _SetCorrupt(fGoCorrupt, __LINE__, REASON_BLOCKSTARTOUTOFRANGE, tyExpected, faBlock, 0xFFFFFFFF, 0xFFFFFFFF);
goto exit;
}
// Determine iView
iViewStart = (faBlock / CB_MAPPED_VIEW);
// Set iViewend
iViewEnd = (faBlock + sizeof(BLOCKHEADER)) / CB_MAPPED_VIEW;
// If the Header Straddles a view boundary...
if (iViewStart != iViewEnd)
{
// Allocate a Special View
IF_FAILEXIT(hr = _AllocateSpecialView(faBlock, g_SystemInfo.dwAllocationGranularity, &pSpecial));
// Set pView
pView = pSpecial;
}
// Otherwise, use a view
else
{
// Validate iView
IxpAssert(iViewStart < m_pStorage->cAllocated);
// Readability
pView = &m_pStorage->prgView[iViewStart];
// Is this View Mapped yet ?
if (NULL == pView->pbView)
{
// Validate the Entry
IxpAssert(0 == pView->faView && 0 == pView->cbView && NULL == pView->pNext);
// Set faView
pView->faView = (iViewStart * CB_MAPPED_VIEW);
// Set cbView
pView->cbView = min(m_pStorage->cbFile - pView->faView, CB_MAPPED_VIEW);
// Map the View
IF_FAILEXIT(hr = DBMapViewOfFile(m_pStorage->hMap, m_pStorage->cbFile, &pView->faView, &pView->cbView, (LPVOID *)&pView->pbView));
// Increment cbMappedSpecial
m_pStorage->cbMappedViews += pView->cbView;
}
}
// De-Ref the Block (Offset from start of the view)
pBlock = (LPBLOCKHEADER)(pView->pbView + (faBlock - pView->faView));
// Mark the block
if (pMark)
{
// Set the Address
pBlock->faBlock = faBlock;
// Set cbBlock
cbBlock = pMark->cbBlock;
// Adjust cbSize
pBlock->cbSize = cbBlock - g_rgcbBlockSize[tyExpected];
}
// Otherwise, validate the block
else
{
// Get Block Size
cbBlock = pBlock->cbSize + g_rgcbBlockSize[tyExpected];
// Check the Block Start Address
if (faBlock != pBlock->faBlock)
{
hr = _SetCorrupt(fGoCorrupt, __LINE__, REASON_UMATCHINGBLOCKADDRESS, tyExpected, faBlock, pBlock->faBlock, cbBlock);
goto exit;
}
// Size of Block is Out-of-range
if (pBlock->faBlock + cbBlock > m_pStorage->cbFile)
{
hr = _SetCorrupt(fGoCorrupt, __LINE__, REASON_BLOCKSIZEOUTOFRANGE, tyExpected, faBlock, pBlock->faBlock, cbBlock);
goto exit;
}
}
// Compute iViewEnd
iViewEnd = ((faBlock + cbBlock) / CB_MAPPED_VIEW);
// Does this block end within the same view, or is the block larger than my view size ?
if (iViewStart != iViewEnd)
{
// If I already allocated a special view...
if (pSpecial)
{
// Validate
IxpAssert(pView == pSpecial);
// Does faBlock + cbBlock fit into pSpecial ?
if ((faBlock - pView->faView) + cbBlock > pView->cbView)
{
// Validate
IxpAssert(pView->pbView);
// Lets Flush It
FlushViewOfFile(pView->pbView, 0);
// Unmap this view
SafeUnmapViewOfFile(pView->pbView);
// Decrement cbMappedSpecial
m_pStorage->cbMappedSpecial -= pView->cbView;
// Set faView
pView->faView = faBlock;
// Set cbView
pView->cbView = cbBlock;
// Map the View
IF_FAILEXIT(hr = DBMapViewOfFile(m_pStorage->hMap, m_pStorage->cbFile, &pView->faView, &pView->cbView, (LPVOID *)&pView->pbView));
// Increment cbMappedSpecial
m_pStorage->cbMappedSpecial += pView->cbView;
}
}
// Otherwise, create a special view
else
{
// Allocate a Special View
IF_FAILEXIT(hr = _AllocateSpecialView(faBlock, cbBlock, &pSpecial));
// Set pView
pView = pSpecial;
}
}
// Validate
IxpAssert((faBlock - pView->faView) + cbBlock <= pView->cbView);
// Return the Block (offset from start of block)
*ppvBlock = (LPVOID)(pView->pbView + (faBlock - pView->faView));
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_ReuseFixedFreeBlock
//--------------------------------------------------------------------------
HRESULT CDatabase::_ReuseFixedFreeBlock(LPFILEADDRESS pfaFreeHead,
BLOCKTYPE tyBlock, DWORD cbExpected, LPVOID *ppvBlock)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faHead=(*pfaFreeHead);
DWORD cbBlock;
LPFREEBLOCK pFree;
// Is there a free block
if (0 == faHead)
return(S_OK);
// Get the Free Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_FREE, faHead, (LPVOID *)&pFree));
// Validate
Assert(cbExpected == pFree->cbBlock);
// Set *ppHeader
*ppvBlock = (LPVOID)pFree;
// Set the New Head Free Chain Block
*pfaFreeHead = pFree->faNext;
// Change the Size
pFree->cbSize = cbExpected - g_rgcbBlockSize[tyBlock];
// Mark the Block
*ppvBlock = (LPVOID)pFree;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_AllocateBlock
//--------------------------------------------------------------------------
HRESULT CDatabase::_AllocateBlock(BLOCKTYPE tyBlock, DWORD cbExtra,
LPVOID *ppvBlock)
{
// Locals
HRESULT hr=S_OK;
DWORD cbBlock;
DWORD iBucket;
// Trace
TraceCall("CDatabase::_AllocateBlock");
// Invalid State
Assert(ppvBlock && BLOCK_ENDOFPAGE != tyBlock && BLOCK_FREE != tyBlock);
// Initialize
*ppvBlock = NULL;
// Add Space Needed to store tyBlock
cbBlock = (g_rgcbBlockSize[tyBlock] + cbExtra);
// Dword Align
cbBlock += DwordAlign(cbBlock);
// Allocating a Chain Block ?
if (BLOCK_CHAIN == tyBlock)
{
// Reuse Free Block...
IF_FAILEXIT(hr = _ReuseFixedFreeBlock(&m_pHeader->faFreeChainBlock, BLOCK_CHAIN, cbBlock, ppvBlock));
}
// Allocating a Stream Block ?
else if (BLOCK_STREAM == tyBlock)
{
// Append Stream Block Size
cbBlock += CB_STREAM_BLOCK;
// Reuse Free Block...
IF_FAILEXIT(hr = _ReuseFixedFreeBlock(&m_pHeader->faFreeStreamBlock, BLOCK_STREAM, cbBlock, ppvBlock));
}
// Otherwise, allocating a record block
else if (cbBlock <= CB_MAX_FREE_BUCKET)
{
// Adjust cbBlock
if (cbBlock < CB_MIN_FREE_BUCKET)
cbBlock = CB_MIN_FREE_BUCKET;
// Compute Free Block Bucket
iBucket = ((cbBlock - CB_MIN_FREE_BUCKET) / CB_FREE_BUCKET);
// Validate
Assert(iBucket < CC_FREE_BUCKETS);
// Is there a Free Block in this bucket
if (m_pHeader->rgfaFreeBlock[iBucket])
{
// PopFreeBlock
_ReuseFixedFreeBlock(&m_pHeader->rgfaFreeBlock[iBucket], tyBlock, cbBlock, ppvBlock);
}
}
// Otherwise
else
{
// Locals
FILEADDRESS faCurrent;
LPFREEBLOCK pCurrent;
LPFREEBLOCK pPrevious=NULL;
// Adjust cbBlock to the next 1k Boundary
cbBlock = (((cbBlock / CB_ALIGN_LARGE) + 1) * CB_ALIGN_LARGE);
// Set faCurrent
faCurrent = m_pHeader->faFreeLargeBlock;
// Loop through free large blocks (Sorted from smallest to largest)
while (faCurrent)
{
// Get the Current Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_FREE, faCurrent, (LPVOID *)&pCurrent));
// If this block is too small...
if (cbBlock <= pCurrent->cbBlock)
{
// Set Next Free Chain Address
if (NULL == pPrevious)
{
// Set First Free Chain
m_pHeader->faFreeLargeBlock = pCurrent->faNext;
}
// Otherwise, relink free chains
else
{
// Set the next block
pPrevious->faNext = pCurrent->faNext;
}
// Reset the Block Types
IF_FAILEXIT(hr = _MarkBlock(tyBlock, faCurrent, cbBlock, ppvBlock));
// Done
break;
}
// Save Previous
pPrevious = pCurrent;
// Set Current
faCurrent = pCurrent->faNext;
}
}
// Didn't find a block to allocate
if (0 == *ppvBlock)
{
// Is there a page with some space on it...
if (BLOCK_CHAIN == tyBlock)
{
// Allocate From Page
ALLOCATEPAGE AllocatePage=m_pHeader->AllocateChain;
// Allocate From Page
IF_FAILEXIT(hr = _AllocateFromPage(BLOCK_CHAIN, &AllocatePage, CB_CHAIN_PAGE, cbBlock, ppvBlock));
// Restore the page info
m_pHeader->AllocateChain = AllocatePage;
}
// Stream Block
else if (BLOCK_STREAM == tyBlock)
{
// Allocate From Page
ALLOCATEPAGE AllocatePage=m_pHeader->AllocateStream;
// Allocate From Page
IF_FAILEXIT(hr = _AllocateFromPage(BLOCK_STREAM, &AllocatePage, CB_STREAM_PAGE, cbBlock, ppvBlock));
// Restore the page info
m_pHeader->AllocateStream = AllocatePage;
}
// Record Block
else
{
// Allocate From Page
ALLOCATEPAGE AllocatePage=m_pHeader->AllocateRecord;
// Allocate From Page
IF_FAILEXIT(hr = _AllocateFromPage(tyBlock, &AllocatePage, CB_VARIABLE_PAGE, cbBlock, ppvBlock));
// Restore the page info
m_pHeader->AllocateRecord = AllocatePage;
}
// Metrics
m_pHeader->cbAllocated += cbBlock;
}
// Otherwise
else
{
// Metrics
m_pHeader->cbFreed -= cbBlock;
}
// Increment
m_pHeader->rgcbAllocated[tyBlock] += cbBlock;
exit:
// We should have found something
Assert(SUCCEEDED(hr) ? *ppvBlock > 0 : TRUE);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FreeBlock
//--------------------------------------------------------------------------
HRESULT CDatabase::_FreeBlock(BLOCKTYPE tyBlock, FILEADDRESS faAddress)
{
// Locals
HRESULT hr=S_OK;
DWORD iBucket;
DWORD cbBlock;
LPFREEBLOCK pFree;
// Trace
TraceCall("CDatabase::_FreeBlock");
// Invalid Args
Assert(BLOCK_FREE != tyBlock);
// Never Free faAddress 0?
if (0 == faAddress)
{
Assert(FALSE);
hr = TraceResult(E_FAIL);
goto exit;
}
#ifdef DEBUG
#ifdef FREBLOCK_VALIDATION
if (BLOCK_RECORD == tyBlock)
_DebugValidateUnrefedRecord(faAddress);
#endif // FREBLOCK_VALIDATION
#endif // DEBUG
// Get the Block
IF_FAILEXIT(hr = _GetBlock(tyBlock, faAddress, (LPVOID *)&pFree));
// Save Block Size
cbBlock = pFree->cbSize + g_rgcbBlockSize[tyBlock];
// Mark Block as Free
pFree->cbSize = cbBlock - g_rgcbBlockSize[BLOCK_FREE];
// Set Block Size
pFree->cbBlock = cbBlock;
// Initialize
pFree->faNext = 0;
// BLOCK_CHAIN
if (BLOCK_CHAIN == tyBlock)
{
// Fill free node header
pFree->faNext = m_pHeader->faFreeChainBlock;
// Set new iFreeChain
m_pHeader->faFreeChainBlock = pFree->faBlock;
}
// BLOCK_STREAM
else if (BLOCK_STREAM == tyBlock)
{
// Fill free node header
pFree->faNext = m_pHeader->faFreeStreamBlock;
// Set new iFreeChain
m_pHeader->faFreeStreamBlock = pFree->faBlock;
}
// Other types of variable length blocks
else if (pFree->cbBlock <= CB_MAX_FREE_BUCKET)
{
// Validate
Assert(pFree->cbBlock >= CB_MIN_FREE_BUCKET && (pFree->cbBlock % 4) == 0);
// Compute Free Block Bucket
iBucket = ((pFree->cbBlock - CB_MIN_FREE_BUCKET) / CB_FREE_BUCKET);
// Fill free node header
pFree->faNext = m_pHeader->rgfaFreeBlock[iBucket];
// Set new iFreeChain
m_pHeader->rgfaFreeBlock[iBucket] = pFree->faBlock;
}
// Otherwise, freeing a large block
else
{
// Must be an integral size of a a large block
Assert((pFree->cbBlock % CB_ALIGN_LARGE) == 0);
// If there are no blocks yet
if (0 == m_pHeader->faFreeLargeBlock)
{
// Set the Head
m_pHeader->faFreeLargeBlock = pFree->faBlock;
}
// Otherwise, link into the sorted list
else
{
// Put this block in sorted order from smallest to the largest...
FILEADDRESS faCurrent;
LPFREEBLOCK pCurrent;
LPFREEBLOCK pPrevious=NULL;
// Set faCurrent
faCurrent = m_pHeader->faFreeLargeBlock;
// Loop through free large blocks (Sorted from smallest to largest)
while (faCurrent)
{
// Get the Current Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_FREE, faCurrent, (LPVOID *)&pCurrent));
// If pBlock is less than pCurrent, then insert after pPreviuos but before pCurrent
if (pFree->cbBlock <= pCurrent->cbBlock)
{
// Previous
if (pPrevious)
{
// Validate
Assert(pPrevious->faNext == faCurrent);
// Set Next
pPrevious->faNext = pFree->faBlock;
}
// Otherwise, adjust the head
else
{
// Validate
Assert(m_pHeader->faFreeLargeBlock == faCurrent);
// Set the Head
m_pHeader->faFreeLargeBlock = pFree->faBlock;
}
// Set pBlock Next
pFree->faNext = faCurrent;
// Done
break;
}
// Next Block is Null ?
else if (0 == pCurrent->faNext)
{
// Append to the End
pCurrent->faNext = pFree->faBlock;
// Done
break;
}
// Save Previous
pPrevious = pCurrent;
// Set Current
faCurrent = pCurrent->faNext;
}
}
}
// Increment
m_pHeader->rgcbAllocated[tyBlock] -= pFree->cbBlock;
// Metrics
m_pHeader->cbFreed += pFree->cbBlock;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetSize
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::GetSize(LPDWORD pcbFile, LPDWORD pcbAllocated,
LPDWORD pcbFreed, LPDWORD pcbStreams)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::GetSize");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Return pcbFile
if (pcbFile)
*pcbFile = m_pStorage->cbFile;
// Return pcbAllocated
if (pcbAllocated)
*pcbAllocated = m_pHeader->cbAllocated;
// Return pcbFreed
if (pcbFreed)
*pcbFreed = (m_pHeader->cbFreed + (m_pStorage->cbFile - m_pHeader->faNextAllocate));
// Return pcbStreams
if (pcbStreams)
*pcbStreams = m_pHeader->rgcbAllocated[BLOCK_STREAM];
exit:
// Unlock the Heap
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetRecordCount
//--------------------------------------------------------------------------
HRESULT CDatabase::GetRecordCount(INDEXORDINAL iIndex, ULONG *pcRecords)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// TraceCall
TraceCall("CDatabase::GetRecordCount");
// Invalid Args
Assert(pcRecords && iIndex < CMAX_INDEXES);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Return the Count
*pcRecords = m_pHeader->rgcRecords[iIndex];
exit:
// Unlock the Heap
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::UpdateRecord
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::UpdateRecord(LPVOID pBinding)
{
// Locals
HRESULT hr=S_OK;
HRESULT hrVisible;
INT nCompare;
DWORD i;
INDEXORDINAL iIndex;
FILEADDRESS faChain;
NODEINDEX iNode;
ROWORDINAL iRow;
FILEADDRESS faOldRecord=0;
FILEADDRESS faNewRecord=0;
BYTE bVersion;
LPVOID pBindingOld=NULL;
LPRECORDBLOCK pRecord;
ORDINALLIST Ordinals;
LPCHAINBLOCK pChain;
RECORDMAP RecordMap;
HLOCK hLock=NULL;
DWORD cNotify=0;
FINDRESULT rgResult[CMAX_INDEXES];
// Trace
TraceCall("CDatabase::UpdateRecord");
// Invalid Args
Assert(pBinding);
// Initialize Ordinals (Initializes everything to INVALID_ROWORDINAL)
FillMemory(&Ordinals, sizeof(ORDINALLIST), 0xFF);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Primary Index Can not change
rgResult[0].fChanged = FALSE;
// Try to find the existing record
IF_FAILEXIT(hr = _FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, pBinding, &rgResult[0].faChain, &rgResult[0].iNode, &Ordinals.rgiRecord1[IINDEX_PRIMARY]));
// If not found, you can't update it. Use Insert
if (DB_S_NOTFOUND == hr)
{
hr = TraceResult(DB_E_NOTFOUND);
goto exit;
}
// Primary Index Can not change
rgResult[0].fFound = TRUE;
// Cast pChain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, rgResult[0].faChain, (LPVOID *)&pChain));
// De-Reference the Record
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, pChain->rgNode[rgResult[0].iNode].faRecord, (LPVOID *)&pRecord));
// Get the Version
bVersion = *((BYTE *)((LPBYTE)pBinding + m_pSchema->ofVersion));
// Version Difference ?
if (pRecord->bVersion != bVersion)
{
hr = TraceResult(DB_E_RECORDVERSIONCHANGED);
goto exit;
}
// More than one index ?
if (m_pHeader->cIndexes > 1 || m_pExtension)
{
// Allocate a Binding
IF_NULLEXIT(pBindingOld = PHeapAllocate(HEAP_ZERO_MEMORY, m_pSchema->cbBinding));
// Read the Record
IF_FAILEXIT(hr = _ReadRecord(pRecord->faBlock, pBindingOld));
}
// Call Extension
if (m_pExtension)
{
// Extend Record Updates
m_pExtension->OnRecordUpdate(OPERATION_BEFORE, NULL, pBindingOld, pBinding);
}
// Loop through the indexes
for (i = 1; i < m_pHeader->cIndexes; i++)
{
// Get iIndex
iIndex = m_pHeader->rgiIndex[i];
// Try to find the existing record
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBindingOld, &rgResult[i].faChain, &rgResult[i].iNode, &Ordinals.rgiRecord1[iIndex]));
// If not found, you can't update it. Use Insert
if (DB_S_FOUND == hr)
{
// We Found the Record
rgResult[i].fFound = TRUE;
// Did Record's Key Change for this Index ?
IF_FAILEXIT(hr = _CompareBinding(iIndex, COLUMNS_ALL, pBinding, pRecord->faBlock, &nCompare));
// Not the Same ?
if (0 != nCompare)
{
// Changed
rgResult[i].fChanged = TRUE;
// Otherwise: Decide Where to insert
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBinding, &faChain, &iNode, &iRow));
// If pBinding is already in this index, then its going to be a duplicate
if (DB_S_FOUND == hr)
{
hr = TraceResult(DB_E_DUPLICATE);
goto exit;
}
}
// Otherwise, the index hasn't changed
else
{
// Assume the Index is Unchanged
rgResult[i].fChanged = FALSE;
}
}
// Otherwise, not found
else
{
// This Index Must be Filtered
Assert(m_rghFilter[iIndex]);
// Not Found
rgResult[i].fFound = FALSE;
// Changed
rgResult[i].fChanged = TRUE;
// First Record Never Existed
Ordinals.rgiRecord1[iIndex] = INVALID_ROWORDINAL;
// See if the new record already exists in this index
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBinding, &faChain, &iNode, &iRow));
// If pBinding is already in this index, then its going to be a duplicate
if (DB_S_FOUND == hr)
{
hr = TraceResult(DB_E_DUPLICATE);
goto exit;
}
}
}
// Save the old node
faOldRecord = pRecord->faBlock;
// Get the Record Size
IF_FAILEXIT(hr = _GetRecordSize(pBinding, &RecordMap));
// Record Shrunk or stayed the same...?
if (RecordMap.cbData + RecordMap.cbTags <= pRecord->cbSize && 0 == m_pShare->cNotifyWithData)
{
// Persist the Record
IF_FAILEXIT(hr = _SaveRecord(pRecord, &RecordMap, pBinding));
// Set faNewRecord
faNewRecord = pRecord->faBlock;
// Validate the Version
Assert(bVersion + 1 == pRecord->bVersion || bVersion + 1 == 256);
}
// Otherwise, record grew in size
else
{
// Don't Use This Again
pRecord = NULL;
// Link the new record into the table
IF_FAILEXIT(hr = _LinkRecordIntoTable(&RecordMap, pBinding, bVersion, &faNewRecord));
}
// Update all the indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Adjustment for filtered indexes
hrVisible = _IsVisible(m_rghFilter[iIndex], pBinding);
// Not Changed ?
if (S_OK == hrVisible && FALSE == rgResult[i].fChanged && TRUE == rgResult[i].fFound)
{
// Record Changed Locations ?
if (faOldRecord != faNewRecord)
{
// Just Update the Address of the New Record
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, rgResult[i].faChain, (LPVOID *)&pChain));
// Update the Chain
pChain->rgNode[rgResult[i].iNode].faRecord = faNewRecord;
}
// Ordinal is Unchanged
Ordinals.rgiRecord2[iIndex] = Ordinals.rgiRecord1[iIndex];
// If Index changed and somebody wanted notifications about this index
cNotify += m_pShare->rgcIndexNotify[iIndex];
}
// Otherwise...
else
{
// If the Record was found, delete it
if (TRUE == rgResult[i].fFound)
{
// Delete the Record from the index
IF_FAILEXIT(hr = _IndexDeleteRecord(iIndex, rgResult[i].faChain, rgResult[i].iNode));
// Adjust Open Rowsets
_AdjustOpenRowsets(iIndex, Ordinals.rgiRecord1[iIndex], OPERATION_DELETE);
// Update Record Count
m_pHeader->rgcRecords[iIndex]--;
// If Index changed and somebody wanted notifications about this index
cNotify += m_pShare->rgcIndexNotify[iIndex];
}
// Visible ?
if (S_OK == hrVisible)
{
// Otherwise: Decide Where to insert
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBinding, &rgResult[i].faChain, &rgResult[i].iNode, &Ordinals.rgiRecord2[iIndex], &rgResult[i].nCompare));
// Not Found
Assert(DB_S_NOTFOUND == hr);
// Do the Insertion
IF_FAILEXIT(hr = _IndexInsertRecord(iIndex, rgResult[i].faChain, faNewRecord, &rgResult[i].iNode, rgResult[i].nCompare));
// Update Record Count
m_pHeader->rgcRecords[iIndex]++;
// Adjust iRow
Ordinals.rgiRecord2[iIndex] += (rgResult[i].iNode + 1);
// Adjust Open Rowsets
_AdjustOpenRowsets(iIndex, Ordinals.rgiRecord2[iIndex], OPERATION_INSERT);
// If Index changed and somebody wanted notifications about this index
cNotify += m_pShare->rgcIndexNotify[iIndex];
}
// Otherwise...
else
{
// Doesn't Exist
Ordinals.rgiRecord2[iIndex] = INVALID_ROWORDINAL;
}
}
}
// Send Notifications ?
if (cNotify > 0)
{
// Send Notifications ?
if (0 == m_pShare->cNotifyWithData)
{
// Build the Update Notification Package
_LogTransaction(TRANSACTION_UPDATE, INVALID_INDEX_ORDINAL, &Ordinals, 0, 0);
}
// Otherwise...
else
{
// Must have copied...
Assert(faOldRecord != faNewRecord);
// Build the Update Notification Package
_LogTransaction(TRANSACTION_UPDATE, INVALID_INDEX_ORDINAL, &Ordinals, faOldRecord, faNewRecord);
}
}
// Otherwise, free the old record
else if (faOldRecord != faNewRecord)
{
// De-allocate the record from the file
IF_FAILEXIT(hr = _FreeRecordStorage(OPERATION_UPDATE, faOldRecord));
}
// Update the Version
bVersion++;
// Store the Version back into the record
*((WORD *)((LPBYTE)pBinding + m_pSchema->ofVersion)) = bVersion;
// Version Change
m_pShare->dwVersion++;
// Call Extension
if (m_pExtension)
{
// Extend Record Updates
m_pExtension->OnRecordUpdate(OPERATION_AFTER, &Ordinals, pBindingOld, pBinding);
}
exit:
// Cleanup
SafeFreeBinding(pBindingOld);
// Unlock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_LinkRecordIntoTable
//--------------------------------------------------------------------------
HRESULT CDatabase::_LinkRecordIntoTable(LPRECORDMAP pMap, LPVOID pBinding,
BYTE bVersion, LPFILEADDRESS pfaRecord)
{
// Locals
HRESULT hr=S_OK;
LPRECORDBLOCK pCurrent;
LPRECORDBLOCK pPrevious;
// Trace
TraceCall("CDatabase::_LinkRecordIntoTable");
// Invalid Args
Assert(pBinding && pfaRecord);
// Allocate a block in the file for the record
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_RECORD, pMap->cbData + pMap->cbTags, (LPVOID *)&pCurrent));
// Set the Version
pCurrent->bVersion = bVersion;
// Persist the Record
IF_FAILEXIT(hr = _SaveRecord(pCurrent, pMap, pBinding));
// Return *pfaRecord
*pfaRecord = pCurrent->faBlock;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_AdjustParentNodeCount
//--------------------------------------------------------------------------
HRESULT CDatabase::_AdjustParentNodeCount(INDEXORDINAL iIndex,
FILEADDRESS faChain, LONG lCount)
{
// De-ref
HRESULT hr=S_OK;
LPCHAINBLOCK pParent;
LPCHAINBLOCK pCurrent;
// Trace
TraceCall("CDatabase::_AdjustParentNodeCount");
// Invalid Arg
Assert(faChain && (1 == lCount || -1 == lCount));
// Set pCurrent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pCurrent));
// Goto the Parent...
while (1)
{
// Goto the Parent
if (0 == pCurrent->faParent)
{
// Better be the root
Assert(pCurrent->faBlock == m_pHeader->rgfaIndex[iIndex] && 0 == pCurrent->iParent);
// Done
break;
}
// Set pCurrent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pCurrent->faParent, (LPVOID *)&pParent));
// Validate
Assert(pCurrent->iParent < pParent->cNodes);
// 0 Node
if (0 == pCurrent->iParent && pParent->faLeftChain == pCurrent->faBlock)
{
// Increment or Decrement Count
pParent->cLeftNodes += lCount;
}
// Otherwise, increment cRightNodes
else
{
// Validate
Assert(pParent->rgNode[pCurrent->iParent].faRightChain == pCurrent->faBlock);
// Increment Right Node Count
pParent->rgNode[pCurrent->iParent].cRightNodes += lCount;
}
// Update pCurrent
pCurrent = pParent;
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::LockNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::LockNotify(LOCKNOTIFYFLAGS dwFlags, LPHLOCK phLock)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::LockNotify");
// Invalid Args
if (NULL == phLock)
return TraceResult(E_INVALIDARG);
// Initialize
*phLock = NULL;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Increment Queue Notify count
m_pShare->cNotifyLock++;
// Store Some Non-null value
*phLock = (HLOCK)m_hMutex;
exit:
// Unlock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::UnlockNotify
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::UnlockNotify(LPHLOCK phLock)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::UnlockNotify");
// Invalid Args
if (NULL == phLock)
return TraceResult(E_INVALIDARG);
// Nothing to Unlock?
if (NULL == *phLock)
return(S_OK);
// Store Some Non-null value
Assert(*phLock == (HLOCK)m_hMutex);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Increment Queue Notify count
m_pShare->cNotifyLock--;
// No more Lock
*phLock = NULL;
// If there are still refs, don't send notifications yet...
if (m_pShare->cNotifyLock)
goto exit;
// Dispatch Pending Notifications
_DispatchPendingNotifications();
exit:
// Unlock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetTransaction
//--------------------------------------------------------------------------
HRESULT CDatabase::GetTransaction(LPHTRANSACTION phTransaction,
LPTRANSACTIONTYPE ptyTransaction, LPVOID pRecord1, LPVOID pRecord2,
LPINDEXORDINAL piIndex, LPORDINALLIST pOrdinals)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
LPTRANSACTIONBLOCK pTransaction;
FILEADDRESS faTransaction;
// Trace
TraceCall("CDatabase::GetTransaction");
// Validate
Assert(phTransaction && ptyTransaction && pOrdinals);
// No Transaction
if (NULL == *phTransaction)
return TraceResult(E_INVALIDARG);
// Setup faTransaction
faTransaction = (FILEADDRESS)PtrToUlong((*phTransaction));
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Get the Transaction Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_TRANSACTION, faTransaction, (LPVOID *)&pTransaction));
// Validate
IxpAssert(pTransaction->cRefs > 0);
// Set tyTransaction
*ptyTransaction = pTransaction->tyTransaction;
// Copy Index
*piIndex = pTransaction->iIndex;
// Copy Ordinals
CopyMemory(pOrdinals, &pTransaction->Ordinals, sizeof(ORDINALLIST));
// Set hNext
(*phTransaction) = (HTRANSACTION)IntToPtr(pTransaction->faNextInBatch);
// Did the caller want Record 1
if (pRecord1 && pTransaction->faRecord1)
{
// Free pRecord1
FreeRecord(pRecord1);
// Read Record 1
IF_FAILEXIT(hr = _ReadRecord(pTransaction->faRecord1, pRecord1));
}
// Read Second Record
if (pRecord2 && pTransaction->faRecord2)
{
// Must be an Update
Assert(TRANSACTION_UPDATE == pTransaction->tyTransaction);
// Free pRecord1
FreeRecord(pRecord2);
// Read Record 2
IF_FAILEXIT(hr = _ReadRecord(pTransaction->faRecord2, pRecord2));
}
// Decrement Refs on this item
pTransaction->cRefs--;
// If hit zero, release it
if (pTransaction->cRefs > 0)
goto exit;
// Free Transact Block
IF_FAILEXIT(hr = _FreeTransactBlock(pTransaction));
exit:
// Unlock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FreeTransactBlock
//--------------------------------------------------------------------------
HRESULT CDatabase::_FreeTransactBlock(LPTRANSACTIONBLOCK pTransaction)
{
// Locals
HRESULT hr=S_OK;
LPTRANSACTIONBLOCK pPrevious;
LPTRANSACTIONBLOCK pNext;
// Trace
TraceCall("CDatabase::_FreeTransactBlock");
// Better be Zero
IxpAssert(0 == pTransaction->cRefs);
IxpAssert(m_pHeader->cTransacts > 0);
// Previous ?
if (pTransaction->faPrevious)
{
// Get the Previous
IF_FAILEXIT(hr = _GetBlock(BLOCK_TRANSACTION, pTransaction->faPrevious, (LPVOID *)&pPrevious));
// Set Previous Next
pPrevious->faNext = pTransaction->faNext;
}
// Otherwise, adjust head
else
{
// Validate
IxpAssert(pTransaction->faBlock == m_pHeader->faTransactHead);
// Adjust Head
m_pHeader->faTransactHead = pTransaction->faNext;
}
// Next ?
if (pTransaction->faNext)
{
// Get the Previous
IF_FAILEXIT(hr = _GetBlock(BLOCK_TRANSACTION, pTransaction->faNext, (LPVOID *)&pNext));
// Set Previous Next
pNext->faPrevious = pTransaction->faPrevious;
}
// Otherwise, adjust head
else
{
// Validate
IxpAssert(pTransaction->faBlock == m_pHeader->faTransactTail);
// Adjust Head
m_pHeader->faTransactTail = pTransaction->faPrevious;
}
// Decrement cTransacts
m_pHeader->cTransacts--;
// If there is a record 1
if (pTransaction->faRecord1)
{
// TRANSACTION_DELETE gets specail case
if (TRANSACTION_DELETE == pTransaction->tyTransaction)
{
// Free the record, we don't need it
IF_FAILEXIT(hr = _FreeRecordStorage(OPERATION_DELETE, pTransaction->faRecord1));
}
// Otherwise, basic freeblock
else
{
// Free Record 1
IF_FAILEXIT(hr = _FreeBlock(BLOCK_RECORD, pTransaction->faRecord1));
}
}
// Read Second Record
if (pTransaction->faRecord2)
{
// Read Record 2
IF_FAILEXIT(hr = _FreeBlock(BLOCK_RECORD, pTransaction->faRecord2));
}
// Free this block
IF_FAILEXIT(hr = _FreeBlock(BLOCK_TRANSACTION, pTransaction->faBlock));
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CleanupTransactList
//--------------------------------------------------------------------------
HRESULT CDatabase::_CleanupTransactList(void)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faCurrent;
LPTRANSACTIONBLOCK pTransaction;
// Trace
TraceCall("CDatabase::_CleanupTransactList");
// Validate
Assert(0 == m_pHeader->faTransactHead ? 0 == m_pHeader->faTransactTail && 0 == m_pHeader->cTransacts : TRUE);
// Set faCurrent
faCurrent = m_pHeader->faTransactHead;
// While we have a current
while (faCurrent)
{
// Get Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_TRANSACTION, faCurrent, (LPVOID *)&pTransaction));
// Set faCurrent
faCurrent = pTransaction->faNext;
// Set cRefs to Zero
pTransaction->cRefs = 0;
// Free It
IF_FAILEXIT(hr = _FreeTransactBlock(pTransaction));
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CopyRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_CopyRecord(FILEADDRESS faRecord, LPFILEADDRESS pfaCopy)
{
// Locals
HRESULT hr=S_OK;
LPRECORDBLOCK pRecordSrc;
LPRECORDBLOCK pRecordDst;
// Trace
TraceCall("CDatabase::_CopyRecord");
// Get Soruce
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, faRecord, (LPVOID *)&pRecordSrc));
// Allocate a New block
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_RECORD, pRecordSrc->cbSize, (LPVOID *)&pRecordDst));
// Get Soruce
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, faRecord, (LPVOID *)&pRecordSrc));
// Set Version
pRecordDst->bVersion = pRecordSrc->bVersion;
// Set cTags
pRecordDst->cTags = pRecordSrc->cTags;
// Copy Data
CopyMemory((LPBYTE)pRecordDst + sizeof(RECORDBLOCK), (LPBYTE)pRecordSrc + sizeof(RECORDBLOCK), pRecordSrc->cbSize);
// Return Address
*pfaCopy = pRecordDst->faBlock;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_LogTransaction
//--------------------------------------------------------------------------
HRESULT CDatabase::_LogTransaction(TRANSACTIONTYPE tyTransaction,
INDEXORDINAL iIndex, LPORDINALLIST pOrdinals, FILEADDRESS faInRecord1,
FILEADDRESS faInRecord2)
{
// Locals
HRESULT hr=S_OK;
LPTRANSACTIONBLOCK pTransaction;
LPTRANSACTIONBLOCK pTail;
FILEADDRESS faRecord1=0;
FILEADDRESS faRecord2=0;
// Trace
TraceCall("CDatabase::_LogTransaction");
// Nobody is registered
if (0 == m_pShare->cNotify)
return(S_OK);
// If there are people who actually want some data...
if (m_pShare->cNotifyWithData > 0)
{
// Initialize
faRecord1 = faInRecord1;
faRecord2 = faInRecord2;
// TRANSACTION_INSERT
if (TRANSACTION_INSERT == tyTransaction)
{
// Copy Record 2
IF_FAILEXIT(hr = _CopyRecord(faInRecord1, &faRecord1));
}
// TRANSACTION_UPDATE
else if (TRANSACTION_UPDATE == tyTransaction)
{
// Copy Record 2
IF_FAILEXIT(hr = _CopyRecord(faInRecord2, &faRecord2));
}
}
// Otherwise, free some stuff...
else if (faInRecord1 > 0)
{
// TRANSACTION_DELETE
if (TRANSACTION_DELETE == tyTransaction)
{
// Free the record, we don't need it
IF_FAILEXIT(hr = _FreeRecordStorage(OPERATION_DELETE, faInRecord1));
}
// TRANSACTION_UPDATE
else if (TRANSACTION_UPDATE == tyTransaction)
{
// Free the record, we don't need it
IF_FAILEXIT(hr = _FreeRecordStorage(OPERATION_UPDATE, faInRecord1));
}
}
// Allocate Notification Block
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_TRANSACTION, 0, (LPVOID *)&pTransaction));
// Set tyTransaction
pTransaction->tyTransaction = tyTransaction;
// Set cRefs
pTransaction->cRefs = (WORD)m_pShare->cNotify;
// Copy iIndex
pTransaction->iIndex = iIndex;
// If there are ordinals
if (pOrdinals)
{
// Save Sequence
CopyMemory(&pTransaction->Ordinals, pOrdinals, sizeof(ORDINALLIST));
}
// Otherwise, fill ordinals with record counts
else
{
// Validate Transaction Type
Assert(TRANSACTION_INDEX_CHANGED == tyTransaction || TRANSACTION_INDEX_DELETED == tyTransaction || TRANSACTION_COMPACTED == tyTransaction);
// Save Sequence
ZeroMemory(&pTransaction->Ordinals, sizeof(ORDINALLIST));
}
// Set the Record Addresses
pTransaction->faRecord1 = faRecord1;
pTransaction->faRecord2 = faRecord2;
// Link Into the Transaction List
pTransaction->faNext = pTransaction->faPrevious = pTransaction->faNextInBatch = 0;
// No Head yet
if (0 == m_pHeader->faTransactHead)
{
// Set Head and Tail
m_pHeader->faTransactHead = pTransaction->faBlock;
}
// Otherwise, append to tail
else
{
// Get the Transaction Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_TRANSACTION, m_pHeader->faTransactTail, (LPVOID *)&pTail));
// Link Into the Transaction List
pTail->faNext = pTransaction->faBlock;
// Set Previous
pTransaction->faPrevious = pTail->faBlock;
}
// Set the Tail
m_pHeader->faTransactTail = pTransaction->faBlock;
// Increment cTransacts
m_pHeader->cTransacts++;
// Are we Queueing Notifications...
if (0 == m_pShare->cNotifyLock)
{
// Validate
IxpAssert(0 == m_pShare->faTransactLockHead && 0 == m_pShare->faTransactLockTail);
// Dispatch Invoke
IF_FAILEXIT(hr = _DispatchNotification((HTRANSACTION)IntToPtr(pTransaction->faBlock)));
}
// Otherwise, build the transaction lock list
else
{
// Set LockHead
if (0 == m_pShare->faTransactLockHead)
{
// Set the header of the locked transactions
m_pShare->faTransactLockHead = pTransaction->faBlock;
}
// Otherwise, append to tail
else
{
// Get the Transaction Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_TRANSACTION, m_pShare->faTransactLockTail, (LPVOID *)&pTail));
// Link Into the Transaction List
pTail->faNextInBatch = pTransaction->faBlock;
}
// Set the Tail
m_pShare->faTransactLockTail = pTransaction->faBlock;
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_AdjustOpenRowsets
//--------------------------------------------------------------------------
HRESULT CDatabase::_AdjustOpenRowsets(INDEXORDINAL iIndex,
ROWORDINAL iRow, OPERATIONTYPE tyOperation)
{
// Locals
LPROWSETINFO pRowset;
DWORD j;
// Trace
TraceCall("CDatabase::_AdjustOpenRowsets");
// Invalid Args
Assert(OPERATION_DELETE == tyOperation || OPERATION_INSERT == tyOperation);
// Update open rowsets
for (j=0; j<m_pShare->Rowsets.cUsed; j++)
{
// Set iRowset
pRowset = &m_pShare->Rowsets.rgRowset[m_pShare->Rowsets.rgiUsed[j]];
// Does this rowset reference this index ?
if (pRowset->iIndex == iIndex)
{
// How does the newly insert/deleted record affect the current position of this rowset ?
if (iRow <= pRowset->iRow)
{
// lAdjust is negative
if (OPERATION_DELETE == tyOperation && pRowset->iRow > 1)
pRowset->iRow--;
// Otherwise, Increment iRow so that we don't duplicate rows...
else
pRowset->iRow += 1;
}
}
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::InsertRecord
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::InsertRecord(LPVOID pBinding)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faRecord;
RECORDMAP RecordMap;
DWORD i;
DWORD cNotify=0;
INDEXORDINAL iIndex;
HLOCK hLock=NULL;
FINDRESULT rgResult[CMAX_INDEXES];
ORDINALLIST Ordinals;
LPRECORDBLOCK pRecord;
// Trace
TraceCall("CDatabase::InsertRecord");
// Invalid Args
Assert(pBinding);
// Initialize Ordinals (Initializes everything to INVALID_ROWORDINAL)
FillMemory(&Ordinals, sizeof(ORDINALLIST), 0xFF);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Watch for Maximum Unique Id ?
if (0xFFFFFFFF != m_pSchema->ofUniqueId)
{
// Get Id
DWORD dwId = *((DWORD *)((LPBYTE)pBinding + m_pSchema->ofUniqueId));
// Reset dwNextId if dwId isn't in the invalid range
if (0 != dwId && dwId > m_pHeader->dwNextId && dwId < RESERVED_ID_MIN)
m_pHeader->dwNextId = dwId;
}
// IDatabaseExtension
if (m_pExtension)
{
// Extend Insert
m_pExtension->OnRecordInsert(OPERATION_BEFORE, NULL, pBinding);
}
// Loop through all the indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Otherwise: Decide Where to insert
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBinding, &rgResult[i].faChain, &rgResult[i].iNode, &Ordinals.rgiRecord1[iIndex], &rgResult[i].nCompare));
// If key already exist, cache list and return
if (DB_S_FOUND == hr)
{
hr = TraceResult(DB_E_DUPLICATE);
goto exit;
}
}
// Get the Record Size
IF_FAILEXIT(hr = _GetRecordSize(pBinding, &RecordMap));
// Link Record Into the Table
IF_FAILEXIT(hr = _LinkRecordIntoTable(&RecordMap, pBinding, 0, &faRecord));
// Version Change
m_pShare->dwVersion++;
// Insert into the indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Visible in live index
if (S_OK == _IsVisible(m_rghFilter[iIndex], pBinding))
{
// Do the Insertion
IF_FAILEXIT(hr = _IndexInsertRecord(iIndex, rgResult[i].faChain, faRecord, &rgResult[i].iNode, rgResult[i].nCompare));
// Update Record Count
m_pHeader->rgcRecords[iIndex]++;
// Adjust iRow
Ordinals.rgiRecord1[iIndex] += (rgResult[i].iNode + 1);
// AdjustOpenRowsets
_AdjustOpenRowsets(iIndex, Ordinals.rgiRecord1[iIndex], OPERATION_INSERT);
// Notification Required ?
cNotify += m_pShare->rgcIndexNotify[iIndex];
}
// Otherwise, adjust the ordinal to indicate that its not in the index
else
{
// Can't be primary
Assert(IINDEX_PRIMARY != iIndex);
// Ordinals.rgiRecord1[iIndex]
Ordinals.rgiRecord1[iIndex] = INVALID_ROWORDINAL;
}
}
// Set the Version
*((DWORD *)((LPBYTE)pBinding + m_pSchema->ofVersion)) = 1;
// Build the Notification Package
if (cNotify > 0)
{
// Build the Package
_LogTransaction(TRANSACTION_INSERT, INVALID_INDEX_ORDINAL, &Ordinals, faRecord, 0);
}
// IDatabaseExtension
if (m_pExtension)
{
// Extend Insert
m_pExtension->OnRecordInsert(OPERATION_AFTER, &Ordinals, pBinding);
}
exit:
// Finish the Operation
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_IndexInsertRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_IndexInsertRecord(INDEXORDINAL iIndex,
FILEADDRESS faChain, FILEADDRESS faRecord, LPNODEINDEX piNode,
INT nCompare)
{
// Locals
HRESULT hr=S_OK;
CHAINNODE Node={0};
LPCHAINBLOCK pChain;
// Trace
TraceCall("CDatabase::_IndexInsertRecord");
// Invalid Args
Assert(faRecord > 0);
Assert(nCompare > 0 || nCompare < 0);
// If we have a root chain, find a place to insert the new record, or see if the record already exists
if (0 == m_pHeader->rgfaIndex[iIndex])
{
// We should not write into 0
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_CHAIN, 0, (LPVOID *)&pChain));
// zero out the block
ZeroBlock(pChain, sizeof(CHAINBLOCK));
// Set faStart
m_pHeader->rgfaIndex[iIndex] = pChain->faBlock;
// Number of nodes in the chain
pChain->cNodes = 1;
// Setup the first node
pChain->rgNode[0].faRecord = faRecord;
// Validate piNode
IxpAssert(*piNode == 0);
// Return piNode
*piNode = 0;
}
// Otherwise
else
{
// De-ref
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Initialize Node
Node.faRecord = faRecord;
// This is very a special increment and has to do with the way a BinarySearch can
// determine the correct insertion point for a node that did not exist in the
// array in which a binary search was performed on.
if (nCompare > 0)
(*piNode)++;
// Expand the Chain
IF_FAILEXIT(hr = _ExpandChain(pChain, (*piNode)));
// Copy the Node
CopyMemory(&pChain->rgNode[(*piNode)], &Node, sizeof(CHAINNODE));
// If Node is FULL, we must do a split insert
if (pChain->cNodes > BTREE_ORDER)
{
// Split the chain
IF_FAILEXIT(hr = _SplitChainInsert(iIndex, faChain));
}
// Mark Chain as dirty, cause we are about to cache it away
else
{
// Increment Parent Record Count
IF_FAILEXIT(hr = _AdjustParentNodeCount(iIndex, faChain, 1));
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::DeleteRecord
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::DeleteRecord(LPVOID pBinding)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
FILEADDRESS faRecord=0;
LPVOID pBindingOld=NULL;
DWORD cNotify=0;
FINDRESULT rgResult[CMAX_INDEXES];
ORDINALLIST Ordinals;
DWORD i;
INDEXORDINAL iIndex;
LPCHAINBLOCK pChain;
// Trace
TraceCall("CDatabase::DeleteRecord");
// Invalid Args
Assert(pBinding);
// Initialize Ordinals (Initializes everything to INVALID_ROWORDINAL)
FillMemory(&Ordinals, sizeof(ORDINALLIST), 0xFF);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Otherwise: Decide Where to insert
IF_FAILEXIT(hr = _FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, pBinding, &rgResult[0].faChain, &rgResult[0].iNode, &Ordinals.rgiRecord1[IINDEX_PRIMARY]));
// If not found, you can't update it. Use Insert
if (DB_S_NOTFOUND == hr)
{
hr = TraceResult(DB_E_NOTFOUND);
goto exit;
}
// Primary Index Can not change
rgResult[0].fFound = TRUE;
// Cast pChain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, rgResult[0].faChain, (LPVOID *)&pChain));
// Set faRecord
faRecord = pChain->rgNode[rgResult[0].iNode].faRecord;
// If this was the first index and there are more indexes, then read the origina record so that indexes are updated correctly
if (m_pHeader->cIndexes > 1 || m_pExtension)
{
// Allocate a Record
IF_NULLEXIT(pBindingOld = PHeapAllocate(HEAP_ZERO_MEMORY, m_pSchema->cbBinding));
// Read the Record
IF_FAILEXIT(hr = _ReadRecord(faRecord, pBindingOld));
}
// IDatabaseExtension
if (m_pExtension)
{
// Extend Delete
m_pExtension->OnRecordDelete(OPERATION_BEFORE, NULL, pBindingOld);
}
// Loop through the indexes
for (i=1; i<m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Otherwise: Decide Where to insert
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBindingOld, &rgResult[i].faChain, &rgResult[i].iNode, &Ordinals.rgiRecord1[iIndex]));
// The, record wasn't found, there must be a filter on the index
if (DB_S_NOTFOUND == hr)
{
// Not found
rgResult[i].fFound = FALSE;
// Invalid Ordinal
Ordinals.rgiRecord1[iIndex] = INVALID_ROWORDINAL;
}
// Otherwise
else
{
// Found
rgResult[i].fFound = TRUE;
// Get the Chain
Assert(SUCCEEDED(_GetBlock(BLOCK_CHAIN, rgResult[i].faChain, (LPVOID *)&pChain)));
// Validation
Assert(faRecord == pChain->rgNode[rgResult[i].iNode].faRecord);
}
}
// Loop through the indexes
for (i=0; i<m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Found ?
if (rgResult[i].fFound)
{
// Lets remove the link from the index first
IF_FAILEXIT(hr = _IndexDeleteRecord(iIndex, rgResult[i].faChain, rgResult[i].iNode));
// Validate Record Count
Assert(m_pHeader->rgcRecords[iIndex] > 0);
// Update Record Count
m_pHeader->rgcRecords[iIndex]--;
// AdjustOpenRowsets
_AdjustOpenRowsets(iIndex, Ordinals.rgiRecord1[iIndex], OPERATION_DELETE);
// Does somebody want a notification about this index ?
cNotify += m_pShare->rgcIndexNotify[iIndex];
}
}
// Notify Somebody ?
if (cNotify > 0)
{
// Build the Update Notification Package
_LogTransaction(TRANSACTION_DELETE, INVALID_INDEX_ORDINAL, &Ordinals, faRecord, 0);
}
// Otherwise, free the record
else
{
// De-allocate the record from the file
IF_FAILEXIT(hr = _FreeRecordStorage(OPERATION_DELETE, faRecord));
}
// Version Change
m_pShare->dwVersion++;
// IDatabaseExtension
if (m_pExtension)
{
// Extend Delete
m_pExtension->OnRecordDelete(OPERATION_AFTER, &Ordinals, pBindingOld);
}
exit:
// Cleanup
SafeFreeBinding(pBindingOld);
// Unlock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::FindRecord
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::FindRecord(INDEXORDINAL iIndex, DWORD cColumns,
LPVOID pBinding, LPROWORDINAL piRow)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faChain;
NODEINDEX iNode;
LPCHAINBLOCK pChain;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::FindRecord");
// Invalid Args
Assert(pBinding);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Find the Record
IF_FAILEXIT(hr = _FindRecord(iIndex, cColumns, pBinding, &faChain, &iNode, piRow));
// If Found, Copy the record
if (DB_S_FOUND == hr)
{
// Get the Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Open the Record into pBinding
IF_FAILEXIT(hr = _ReadRecord(pChain->rgNode[iNode].faRecord, pBinding));
// Found It
hr = DB_S_FOUND;
// Done
goto exit;
}
// Not Found
hr = DB_S_NOTFOUND;
exit:
// Unlock the index
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_GetChainByIndex
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetChainByIndex(INDEXORDINAL iIndex, ROWORDINAL iRow,
LPFILEADDRESS pfaChain, LPNODEINDEX piNode)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faChain;
LPCHAINBLOCK pChain;
DWORD cLeftNodes=0;
NODEINDEX i;
// Trace
TraceCall("CDatabase::_GetChainByIndex");
// Invalid Args
Assert(pfaChain && piNode);
// Initialize
faChain = m_pHeader->rgfaIndex[iIndex];
// Loop
while (faChain)
{
// Corrupt
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Are there left nodes ?
if (pChain->cLeftNodes > 0 && iRow <= (cLeftNodes + pChain->cLeftNodes))
{
// Goto the left
faChain = pChain->faLeftChain;
}
// Otherwise...
else
{
// Increment cLeftNodes
cLeftNodes += pChain->cLeftNodes;
// Loop throug right chains
for (i=0; i<pChain->cNodes; i++)
{
// Failure
if (cLeftNodes + 1 == iRow)
{
// We found the Chain
*pfaChain = faChain;
// Return the Node Index
*piNode = i;
// Done
goto exit;
}
// Increment cLeftNodes
cLeftNodes++;
// Goto the Right ?
if (iRow <= (cLeftNodes + pChain->rgNode[i].cRightNodes))
{
// Goto the Right
faChain = pChain->rgNode[i].faRightChain;
// Break
break;
}
// First Node ?
cLeftNodes += pChain->rgNode[i].cRightNodes;
}
// Nothing found...
if (i == pChain->cNodes)
break;
}
}
// Not Found
hr = E_FAIL;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::CreateRowset
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::CreateRowset(INDEXORDINAL iIndex,
CREATEROWSETFLAGS dwFlags, LPHROWSET phRowset)
{
// Locals
HRESULT hr=S_OK;
ROWSETORDINAL iRowset;
LPROWSETTABLE pTable;
LPROWSETINFO pRowset;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::CreateRowset");
// Invalid Args
Assert(iIndex < CMAX_INDEXES && phRowset);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// De-Ref the RowsetTable
pTable = &m_pShare->Rowsets;
// Init Rowset Table
if (FALSE == pTable->fInitialized)
{
// Init the rgiFree Array
for (iRowset=0; iRowset<CMAX_OPEN_ROWSETS; iRowset++)
{
// set rgiFree
pTable->rgiFree[iRowset] = iRowset;
// Set rgRowset
pTable->rgRowset[iRowset].iRowset = iRowset;
}
// Set cFree
pTable->cFree = CMAX_OPEN_ROWSETS;
// Initialized
pTable->fInitialized = TRUE;
}
// No free Rowsets ?
if (0 == pTable->cFree)
{
hr = TraceResult(DB_E_TOOMANYOPENROWSETS);
goto exit;
}
// Get a Free Rowset...
iRowset = pTable->rgiFree[pTable->cFree - 1];
// Set phRowset (Need to Add one so that I don't return a NULL
*phRowset = (HROWSET)IntToPtr(iRowset + 1);
// Set pRowset...
pRowset = &pTable->rgRowset[iRowset];
// Validate iRowset
Assert(pRowset->iRowset == iRowset);
// Zero the Roset
ZeroMemory(pRowset, sizeof(ROWSETINFO));
// Set iRowset
pRowset->iRowset = iRowset;
// set iRow
pRowset->iRow = 1;
// Set Index
pRowset->iIndex = iIndex;
// Remove iRowset from rgiFree
pTable->cFree--;
// Put iRowset into rgiUsed
pTable->rgiUsed[pTable->cUsed] = iRowset;
// Increment cUsed
pTable->cUsed++;
exit:
// Enter Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::CloseRowset
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::CloseRowset(LPHROWSET phRowset)
{
// Locals
HRESULT hr=S_OK;
BYTE i;
LPROWSETTABLE pTable;
HLOCK hLock=NULL;
ROWSETORDINAL iRowset=((ROWSETORDINAL)(*phRowset) - 1);
LPROWSETINFO pRowset;
// Nothing ?
if (NULL == *phRowset)
return(S_OK);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Get the Rowset
pRowset = &m_pShare->Rowsets.rgRowset[iRowset];
// Validate
Assert(iRowset == pRowset->iRowset);
// De-Ref the RowsetTable
pTable = &m_pShare->Rowsets;
// Search rgiUsed
for (i=0; i<pTable->cUsed; i++)
{
// Is this It ?
if (pTable->rgiUsed[i] == pRowset->iRowset)
{
// Remove this Rowset
MoveMemory(&pTable->rgiUsed[i], &pTable->rgiUsed[i + 1], sizeof(ROWSETORDINAL) * (pTable->cUsed - (i + 1)));
// Decrement cUsed
pTable->cUsed--;
// Put iRowset into the free list
pTable->rgiFree[pTable->cFree] = pRowset->iRowset;
// Increment cFree
pTable->cFree++;
// Done
break;
}
}
// Don't Free Again
*phRowset = NULL;
exit:
// Enter Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetRowOrdinal
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::GetRowOrdinal(INDEXORDINAL iIndex,
LPVOID pBinding, LPROWORDINAL piRow)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faChain;
NODEINDEX iNode;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::GetRowOrdinal");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Invalid Arg
Assert(pBinding && piRow && iIndex < CMAX_INDEXES);
// Simply do a find record...
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBinding, &faChain, &iNode, piRow));
// No Found
if (DB_S_NOTFOUND == hr)
{
hr = DB_E_NOTFOUND;
goto exit;
}
exit:
// Process / Thread Safety
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::SeekRowset
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::SeekRowset(HROWSET hRowset, SEEKROWSETTYPE tySeek,
LONG cRows, LPROWORDINAL piRowNew)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
ROWORDINAL iRowNew;
ROWSETORDINAL iRowset=((ROWSETORDINAL)(hRowset) - 1);
LPROWSETINFO pRowset;
// Trace
TraceCall("CDatabase::SeekRowset");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Get the Rowset
pRowset = &m_pShare->Rowsets.rgRowset[iRowset];
// Validate
Assert(iRowset == pRowset->iRowset);
// If there are no records, then seek operation is meaningless
if (0 == m_pHeader->rgcRecords[pRowset->iIndex])
{
hr = DB_E_NORECORDS;
goto exit;
}
// Seek From Beginning
if (SEEK_ROWSET_BEGIN == tySeek)
{
// Set iRow (and remember 0th row from beginning is row #1)
iRowNew = (cRows + 1);
}
// Seek From Current Position
else if (SEEK_ROWSET_CURRENT == tySeek)
{
// Adjust iRow
iRowNew = (pRowset->iRow + cRows);
}
// SEEK_ROWSET_END
else if (SEEK_ROWSET_END == tySeek)
{
// Adjust iRow
iRowNew = m_pHeader->rgcRecords[pRowset->iIndex] + cRows;
}
// Error
if (iRowNew > m_pHeader->rgcRecords[pRowset->iIndex] || iRowNew <= 0)
{
hr = E_UNEXPECTED;
goto exit;
}
// Set New iRow
pRowset->iRow = iRowNew;
// Return piRowNew ?
if (piRowNew)
*piRowNew = pRowset->iRow;
exit:
// Process / Thread Safety
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::QueryRowset
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::QueryRowset(HROWSET hRowset, LONG lWanted,
LPVOID *prgpRecord, LPDWORD pcObtained)
{
// Locals
HRESULT hr=S_OK;
DWORD cRead=0;
FILEADDRESS faChain;
NODEINDEX iNode;
LPCHAINBLOCK pChain;
DWORD cWanted=abs(lWanted);
HLOCK hLock=NULL;
LONG lDirection=(lWanted < 0 ? -1 : 1);
ROWSETORDINAL iRowset=((ROWSETORDINAL)(hRowset) - 1);
LPROWSETINFO pRowset;
// Trace
TraceCall("CDatabase::GetRows");
// Invalid Args
Assert(prgpRecord && hRowset);
// Initialize
if (pcObtained)
*pcObtained = 0;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Get the Rowset
pRowset = &m_pShare->Rowsets.rgRowset[iRowset];
// Validate
Assert(iRowset == pRowset->iRowset);
// Invalid ?
if (0 == pRowset->iRow || pRowset->iRow > m_pHeader->rgcRecords[pRowset->iIndex])
{
hr = S_FALSE;
goto exit;
}
// While we have a record address
while (cRead < cWanted)
{
// Get the chain from the index
if (FAILED(_GetChainByIndex(pRowset->iIndex, pRowset->iRow, &faChain, &iNode)))
{
// Done
pRowset->iRow = 0xffffffff;
// Done
break;
}
// De-reference the Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Open the Record into pBinding
IF_FAILEXIT(hr = _ReadRecord(pChain->rgNode[iNode].faRecord, ((LPBYTE)prgpRecord + (m_pSchema->cbBinding * cRead))));
// Increment cRead
cRead++;
// Validate
Assert(pRowset->iRow > 0 && pRowset->iRow <= m_pHeader->rgcRecords[pRowset->iIndex]);
// Increment Index
pRowset->iRow += lDirection;
// If this is a child chain, I can walk all the nodes
if (0 == pChain->faLeftChain)
{
// Better not have any left nodes
Assert(0 == pChain->cLeftNodes);
// Forward ?
if (lDirection > 0)
{
// Loop
for (NODEINDEX i=iNode + 1; i<pChain->cNodes && cRead < cWanted; i++)
{
// Better not have a right chain or any right nodes
Assert(0 == pChain->rgNode[i].faRightChain && 0 == pChain->rgNode[i].cRightNodes);
// Open the Record into pBinding
IF_FAILEXIT(hr = _ReadRecord(pChain->rgNode[i].faRecord, ((LPBYTE)prgpRecord + (m_pSchema->cbBinding * cRead))));
// Increment cRead
cRead++;
// Increment Index
pRowset->iRow += 1;
}
}
// Otherwise, backwards
else
{
// Loop
for (LONG i=iNode - 1; i>=0 && cRead < cWanted; i--)
{
// Better not have a right chain or any right nodes
Assert(0 == pChain->rgNode[i].faRightChain && 0 == pChain->rgNode[i].cRightNodes);
// Open the Record into pBinding
IF_FAILEXIT(hr = _ReadRecord(pChain->rgNode[i].faRecord, ((LPBYTE)prgpRecord + (m_pSchema->cbBinding * cRead))));
// Increment cRead
cRead++;
// Increment Index
pRowset->iRow -= 1;
}
}
}
}
// Set pcbObtained
if (pcObtained)
*pcObtained = cRead;
// Set hr
hr = (cRead > 0) ? S_OK : S_FALSE;
exit:
// Enter Lock
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::FreeRecord
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::FreeRecord(LPVOID pBinding)
{
// Locals
LPVOID pFree;
// Trace
TraceCall("CDatabase::FreeRecord");
// Invalid Args
Assert(pBinding);
// Get the pointer to free
pFree = *((LPVOID *)((LPBYTE)pBinding + m_pSchema->ofMemory));
// Not NULL
if (pFree)
{
// Don't Free again
*((LPVOID *)((LPBYTE)pBinding + m_pSchema->ofMemory)) = NULL;
// Free This
HeapFree(pFree);
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_GetRecordSize
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetRecordSize(LPVOID pBinding, LPRECORDMAP pMap)
{
// Locals
DWORD i;
LPCTABLECOLUMN pColumn;
// Trace
TraceCall("CDatabase::_GetRecordSize");
// Invalid Args
Assert(pBinding && pMap);
// Initialize
ZeroMemory(pMap, sizeof(RECORDMAP));
// Walk through the members in the structure
for (i=0; i<m_pSchema->cColumns; i++)
{
// Readability
pColumn = &m_pSchema->prgColumn[i];
// Is Data Set ?
if (FALSE == DBTypeIsDefault(pColumn, pBinding))
{
// Compute Amount of Data to Store
pMap->cbData += DBTypeGetSize(pColumn, pBinding);
// Count Tags
pMap->cTags++;
// Compute Amount of Tags to Store
pMap->cbTags += sizeof(COLUMNTAG);
}
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_SaveRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_SaveRecord(LPRECORDBLOCK pRecord, LPRECORDMAP pMap,
LPVOID pBinding)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
DWORD cbOffset=0;
DWORD cTags=0;
LPCTABLECOLUMN pColumn;
LPCOLUMNTAG pTag;
// Trace
TraceCall("CDatabase::_SaveRecord");
// Invalid Args
Assert(pRecord && pRecord->faBlock > 0 && pBinding);
// Better have enough space
Assert(pMap->cbData + pMap->cbTags <= pRecord->cbSize && pMap->cbTags == (pMap->cTags * sizeof(COLUMNTAG)));
// Set prgTag
pMap->prgTag = (LPCOLUMNTAG)((LPBYTE)pRecord + sizeof(RECORDBLOCK));
// Set pbData
pMap->pbData = (LPBYTE)((LPBYTE)pRecord + sizeof(RECORDBLOCK) + pMap->cbTags);
// Walk through the members in the structure
for (i=0; i<m_pSchema->cColumns; i++)
{
// Readability
pColumn = &m_pSchema->prgColumn[i];
// Is Data Set ?
if (FALSE == DBTypeIsDefault(pColumn, pBinding))
{
// Compute Hash
pTag = &pMap->prgTag[cTags];
// Set Tag Id
pTag->iColumn = pColumn->iOrdinal;
// Assume pTag Doesn't Contain Data
pTag->fData = 0;
// Store the Offset
pTag->Offset = cbOffset;
// WriteBindTypeData
cbOffset += DBTypeWriteValue(pColumn, pBinding, pTag, pMap->pbData + cbOffset);
// Count Tags
cTags++;
// Validate
Assert(cbOffset <= pMap->cbData);
// Done ?
if (cTags == pMap->cTags)
{
// Should have Wrote Everything
Assert(cbOffset == pMap->cbData);
// Done
break;
}
}
}
// Increment Record Version
pRecord->bVersion++;
// Write the number of columns
pRecord->cTags = pMap->cTags;
// Validate
Assert(cTags == pMap->cTags && pRecord->cTags > 0);
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_ReadRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_ReadRecord(FILEADDRESS faRecord, LPVOID pBinding,
BOOL fInternal /* = FALSE */)
{
// Locals
HRESULT hr=S_OK;
LPBYTE pbData=NULL;
LPCTABLECOLUMN pColumn;
LPCOLUMNTAG pTag;
WORD iTag;
RECORDMAP Map;
LPRECORDBLOCK pRecord;
// Trace
TraceCall("CDatabase::_ReadRecord");
// Invalid Args
Assert(faRecord > 0 && pBinding);
// Zero pBinding
ZeroMemory(pBinding, m_pSchema->cbBinding);
// Bad Chain Node
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, faRecord, (LPVOID *)&pRecord));
// Get Record Map
IF_FAILEXIT(hr = _GetRecordMap(TRUE, pRecord, &Map));
// Not Internal
if (FALSE == fInternal)
{
// Allocate a record
IF_NULLEXIT(pbData = (LPBYTE)PHeapAllocate(0, Map.cbData));
// Just read the Record
CopyMemory(pbData, Map.pbData, Map.cbData);
// Copy Data From pbData...
Map.pbData = pbData;
}
// Walk through the Tags of the Record
for (iTag=0; iTag<Map.cTags; iTag++)
{
// Readability
pTag = &Map.prgTag[iTag];
// Validate the Tag
if (pTag->iColumn < m_pSchema->cColumns)
{
// De-ref the Column
pColumn = &m_pSchema->prgColumn[pTag->iColumn];
// Read the Data
DBTypeReadValue(pColumn, pTag, &Map, pBinding);
}
}
// Store the version of the record into the binding
*((BYTE *)((LPBYTE)pBinding + m_pSchema->ofVersion)) = pRecord->bVersion;
// Store a point to the blob and free it later
*((LPVOID *)((LPBYTE)pBinding + m_pSchema->ofMemory)) = (LPVOID)pbData;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::BindRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::BindRecord(LPRECORDMAP pMap, LPVOID pBinding)
{
// Locals
WORD iTag;
LPCOLUMNTAG pTag;
LPCTABLECOLUMN pColumn;
// Trace
TraceCall("CDatabase::BindRecord");
// Zero Out the Binding
ZeroMemory(pBinding, m_pSchema->cbBinding);
// Walk through the Tags of the Record
for (iTag=0; iTag<pMap->cTags; iTag++)
{
// Readability
pTag = &pMap->prgTag[iTag];
// Validate the Tag
Assert(pTag->iColumn < m_pSchema->cColumns);
// De-ref the Column
pColumn = &m_pSchema->prgColumn[pTag->iColumn];
// Read the Data
DBTypeReadValue(pColumn, pTag, pMap, pBinding);
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_IsVisible (S_OK = Show, S_FALSE = Hide)
//--------------------------------------------------------------------------
HRESULT CDatabase::_IsVisible(HQUERY hQuery, LPVOID pBinding)
{
// Trace
TraceCall("CDatabase::_IsVisible");
// No hQuery, the record must be visible
if (NULL == hQuery)
return(S_OK);
// Evaluate the Query
return(EvaluateQuery(hQuery, pBinding, m_pSchema, this, m_pExtension));
}
//--------------------------------------------------------------------------
// CDatabase::_CompareBinding
//--------------------------------------------------------------------------
HRESULT CDatabase::_CompareBinding(INDEXORDINAL iIndex, DWORD cColumns,
LPVOID pBinding, FILEADDRESS faRecord, INT *pnCompare)
{
// Locals
HRESULT hr=S_OK;
DWORD cKeys;
LPRECORDBLOCK pBlock;
RECORDMAP Map;
LPCOLUMNTAG pTag;
DWORD iKey;
LPTABLEINDEX pIndex;
LPCTABLECOLUMN pColumn;
// Trace
TraceCall("CDatabase::_CompareBinding");
// Initialize
*pnCompare = 1;
// Get the Right Record Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, faRecord, (LPVOID *)&pBlock));
// Get the Right Node Data
IF_FAILEXIT(hr = _GetRecordMap(TRUE, pBlock, &Map));
// De-Ref the Index
pIndex = &m_pHeader->rgIndexInfo[iIndex];
// Compute Number of keys to match (possible partial index search)
cKeys = min(cColumns, pIndex->cKeys);
// Loop through the key members
for (iKey=0; iKey<cKeys; iKey++)
{
// Readability
pColumn = &m_pSchema->prgColumn[pIndex->rgKey[iKey].iColumn];
// Get Tag From Column Ordinal
pTag = PTagFromOrdinal(&Map, pColumn->iOrdinal);
// Compare Types
*pnCompare = DBTypeCompareBinding(pColumn, &pIndex->rgKey[iKey], pBinding, pTag, &Map);
// Done
if (0 != *pnCompare)
break;
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_PartialIndexCompare
//--------------------------------------------------------------------------
HRESULT CDatabase::_PartialIndexCompare(INDEXORDINAL iIndex, DWORD cColumns,
LPVOID pBinding, LPCHAINBLOCK *ppChain, LPNODEINDEX piNode, LPROWORDINAL piRow)
{
// Locals
HRESULT hr=S_OK;
ROWORDINAL iRow=(piRow ? *piRow : 0xffffffff);
LONG iNode=(*piNode);
LPCHAINBLOCK pChain=(*ppChain);
FILEADDRESS faChain;
INT nCompare=0;
BYTE fFirstLoop;
// Trace
TraceCall("CDatabase::_PartialIndexCompare");
// Invalid Args
Assert(pBinding && pChain && piNode && *piNode < pChain->cNodes);
// Loop
while (1)
{
// Assume no more chains
faChain = 0;
// First Loop
fFirstLoop = TRUE;
// Loop through Current
while (1)
{
// Validate iNode
Assert(iNode >= 0 && iNode < BTREE_ORDER);
// Only do this on every iteration except the first
if (FALSE == fFirstLoop)
{
// Decrement iRow
iRow -= pChain->rgNode[iNode].cRightNodes;
}
// Compare with first node in this chain
IF_FAILEXIT(hr = _CompareBinding(iIndex, cColumns, pBinding, pChain->rgNode[iNode].faRecord, &nCompare));
// Validate nCompare
Assert(0 == nCompare || nCompare > 0);
// pBinding == Node
if (0 == nCompare)
{
// Set new Found iNode
*piNode = (NODEINDEX)iNode;
// Set pFound
*ppChain = pChain;
// Update piRow
if (piRow)
{
// Update piRow
(*piRow) = iRow;
}
// Should we goto the left ?
if (0 == iNode)
{
// Set faNextChain
faChain = pChain->faLeftChain;
// Updating piRow
if (piRow)
{
// Decrement iRow
iRow -= pChain->cLeftNodes;
// Decrement One More ?
iRow--;
}
// Done
break;
}
}
// If pBinding > Node
else if (nCompare > 0)
{
// Set faNextChain
faChain = pChain->rgNode[iNode].faRightChain;
// Done
break;
}
// Decrement iNode
iNode--;
// Decrement iRow
iRow--;
// No Longer the First Loop
fFirstLoop = FALSE;
}
// Done
if (0 == faChain)
break;
// Get the Current Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Reset iNode
iNode = pChain->cNodes - 1;
// Update piRow
if (piRow)
{
// Increment piRow with cLeftNodes
iRow += pChain->cLeftNodes;
// Include this node
iRow++;
// Loop
for (NODEINDEX i = 1; i <= pChain->cNodes - 1; i++)
{
// Increment with cRightNodes
iRow += pChain->rgNode[i - 1].cRightNodes;
// Include this node
iRow++;
}
}
}
#ifdef DEBUG
#ifdef PARTIAL_COMPARE_VALIDATE
if (piRow)
{
ROWORDINAL iOrdinal;
LPVOID pTmpBind = PHeapAllocate(HEAP_ZERO_MEMORY, m_pSchema->cbBinding);
IxpAssert(pTmpBind);
IxpAssert(SUCCEEDED(_ReadRecord((*ppChain)->rgNode[(*piNode)].faRecord, pTmpBind)));
IxpAssert(SUCCEEDED(GetRowOrdinal(iIndex, pTmpBind, &iOrdinal)));
IxpAssert(*piRow == iOrdinal);
SafeFreeBinding(pTmpBind);
IxpAssert(SUCCEEDED(_CompareBinding(iIndex, cColumns, pBinding, &(*ppChain)->rgNode[(*piNode)], &nCompare)));
IxpAssert(0 == nCompare);
}
#endif
#endif
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FindRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_FindRecord(INDEXORDINAL iIndex, DWORD cColumns,
LPVOID pBinding, LPFILEADDRESS pfaChain, LPNODEINDEX piNode,
LPROWORDINAL piRow /*=NULL*/, INT *pnCompare /*=NULL*/)
{
// Locals
HRESULT hr=S_OK;
LONG lLower;
LONG lUpper;
LONG lMiddle=0;
LONG lLastMiddle;
INT nCompare=-1;
DWORD iKey;
BOOL fPartial;
LPCHAINBLOCK pChain;
FILEADDRESS faChain;
// Trace
TraceCall("CDatabase::_FindRecord");
// Invalid Args
Assert(pBinding && pfaChain && piNode && iIndex < CMAX_INDEXES);
// Partial Index Search ?
fPartial = (COLUMNS_ALL == cColumns || cColumns == m_pHeader->rgIndexInfo[iIndex].cKeys) ? FALSE : TRUE;
// Initialize
*pfaChain = 0;
*piNode = 0;
// Start chain address
faChain = m_pHeader->rgfaIndex[iIndex];
// Row Ordinal
if (piRow)
*piRow = 0;
// Loop
while (faChain)
{
// Get the Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Set *pfaChain
*pfaChain = pChain->faBlock;
// Compute initial upper and lower bounds
lLower = 0;
lUpper = pChain->cNodes - 1;
// Do binary search / insert
while (lLower <= lUpper)
{
// Compute middle record to compare against
lMiddle = (BYTE)((lLower + lUpper) / 2);
// Do the Comparison
IF_FAILEXIT(hr = _CompareBinding(iIndex, cColumns, pBinding, pChain->rgNode[lMiddle].faRecord, &nCompare));
// Partial Index Searching
if (0 == nCompare)
{
// Validate
Assert(lMiddle >= 0 && lMiddle <= BTREE_ORDER);
// Set the found node
*piNode = (BYTE)lMiddle;
// Return *pnCompare
if (pnCompare)
*pnCompare = 0;
// Compute piRow
if (piRow)
{
// Increment piRow with cLeftNodes
(*piRow) += pChain->cLeftNodes;
// Include this node
(*piRow)++;
// Loop
for (NODEINDEX iNode=1; iNode<=lMiddle; iNode++)
{
// Increment with cRightNodes
(*piRow) += pChain->rgNode[iNode - 1].cRightNodes;
// Include this node
(*piRow)++;
}
}
// Partial Search
if (fPartial)
{
// Handle Partial Search
IF_FAILEXIT(hr = _PartialIndexCompare(iIndex, cColumns, pBinding, &pChain, piNode, piRow));
// Set *pfaChain
*pfaChain = pChain->faBlock;
}
// We found it
hr = DB_S_FOUND;
// Done
goto exit;
}
// Save last middle position
lLastMiddle = lMiddle;
// Compute upper and lower
if (nCompare > 0)
lLower = lMiddle + 1;
else
lUpper = lMiddle - 1;
}
// No match was found, is lpSearchKey less than last middle ?
if (nCompare < 0 && 0 == lLastMiddle)
{
// Goto the left, there is no need to update piRow
faChain = pChain->faLeftChain;
}
// Otherwise
else
{
// If nCompare is less than zero, then...
if (nCompare < 0)
lLastMiddle--;
// Compute piRow
if (piRow && pChain->rgNode[lLastMiddle].faRightChain)
{
// Increment piRow with cLeftNodes
(*piRow) += pChain->cLeftNodes;
// Include this node
(*piRow)++;
// Loop
for (NODEINDEX iNode=1; iNode<=lLastMiddle; iNode++)
{
// Increment with cRightNodes
(*piRow) += pChain->rgNode[iNode - 1].cRightNodes;
// Include this node
(*piRow)++;
}
}
// Goto the Right
faChain = pChain->rgNode[lLastMiddle].faRightChain;
}
}
// Set piNode
*piNode = (NODEINDEX)lMiddle;
// Return *pnCompare
if (pnCompare)
*pnCompare = nCompare;
// We didn't find it
hr = DB_S_NOTFOUND;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_ExpandChain
//--------------------------------------------------------------------------
HRESULT CDatabase::_ExpandChain(LPCHAINBLOCK pChain, NODEINDEX iNodeBase)
{
// Locals
HRESULT hr=S_OK;
CHAR iNode;
LPCHAINBLOCK pRightChain;
// Trace
TraceCall("CDatabase::_ExpandChain");
// Invalid Args
Assert(pChain && pChain->cNodes > 0 && pChain->cNodes < BTREE_ORDER + 1);
Assert(iNodeBase <= pChain->cNodes);
// Loop from iNode to cNodes
for (iNode = pChain->cNodes - 1; iNode >= iNodeBase; iNode--)
{
// Propagate this node up one level
CopyMemory(&pChain->rgNode[iNode + 1], &pChain->rgNode[iNode], sizeof(CHAINNODE));
// If there is a right chain
if (pChain->rgNode[iNode].faRightChain)
{
// Get the Right Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pChain->rgNode[iNode].faRightChain, (LPVOID *)&pRightChain));
// Validate the current Parent
Assert(pRightChain->faParent == pChain->faBlock);
// Validate the current index
Assert(pRightChain->iParent == iNode);
// Reset the Parent
pRightChain->iParent = iNode + 1;
}
}
// Increment Node Count
pChain->cNodes++;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_ChainInsert
//--------------------------------------------------------------------------
HRESULT CDatabase::_ChainInsert(INDEXORDINAL iIndex, LPCHAINBLOCK pChain,
LPCHAINNODE pNodeLeft, LPNODEINDEX piNodeIndex)
{
// Locals
HRESULT hr=S_OK;
CHAR iNode;
DWORD iKey;
INT nCompare;
LPRECORDBLOCK pBlock;
LPCHAINNODE pNodeRight;
LPCOLUMNTAG pTagLeft;
LPCOLUMNTAG pTagRight;
RECORDMAP MapLeft;
RECORDMAP MapRight;
LPTABLEINDEX pIndex;
LPCTABLECOLUMN pColumn;
// Trace
TraceCall("CDatabase::_ChainInsert");
// Invalid Args
Assert(pChain && pNodeLeft && pChain->cNodes > 0);
// Get the Record Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, pNodeLeft->faRecord, (LPVOID *)&pBlock));
// Get the Record Map
IF_FAILEXIT(hr = _GetRecordMap(TRUE, pBlock, &MapLeft));
// De-Reference the Index
pIndex = &m_pHeader->rgIndexInfo[iIndex];
// Insert into chain
for (iNode = pChain->cNodes - 1; iNode >= 0; iNode--)
{
// Set pNodeRight
pNodeRight = &pChain->rgNode[iNode];
// Get the Record Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_RECORD, pNodeRight->faRecord, (LPVOID *)&pBlock));
// Get the Left Node
IF_FAILEXIT(hr = _GetRecordMap(TRUE, pBlock, &MapRight));
// Loop through the key members
for (iKey=0; iKey<pIndex->cKeys; iKey++)
{
// Readability
pColumn = &m_pSchema->prgColumn[pIndex->rgKey[iKey].iColumn];
// Get Left Tag
pTagLeft = PTagFromOrdinal(&MapLeft, pColumn->iOrdinal);
// Get the Right Tag
pTagRight = PTagFromOrdinal(&MapRight, pColumn->iOrdinal);
// Compare Types
nCompare = DBTypeCompareRecords(pColumn, &pIndex->rgKey[iKey], pTagLeft, pTagRight, &MapLeft, &MapRight);
// Done
if (0 != nCompare)
break;
}
// Insert in this node ?
if (nCompare >= 0)
break;
}
// Expand the Chain
IF_FAILEXIT(hr = _ExpandChain(pChain, iNode + 1));
// Final Insert
CopyMemory(&pChain->rgNode[iNode + 1], pNodeLeft, sizeof(CHAINNODE));
// Node
*piNodeIndex = iNode + 1;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_SplitChainInsert
//--------------------------------------------------------------------------
HRESULT CDatabase::_SplitChainInsert(INDEXORDINAL iIndex, FILEADDRESS faSplit)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
NODEINDEX iNode;
CHAINBLOCK Split;
DWORD cLeftNodes;
DWORD cRightNodes;
FILEADDRESS faChain;
FILEADDRESS faLeftChain;
FILEADDRESS faRightChain;
LPCHAINBLOCK pLeft;
LPCHAINBLOCK pRight;
LPCHAINBLOCK pParent;
LPCHAINBLOCK pSplit;
// Trace
TraceCall("CDatabase::_SplitChainInsert");
// Allocate Space for this chain
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_CHAIN, 0, (LPVOID *)&pLeft));
// Set faLeftChain
faLeftChain = pLeft->faBlock;
// Zero Allocate
ZeroBlock(pLeft, sizeof(CHAINBLOCK));
// Get Split Chain Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faSplit, (LPVOID *)&pSplit));
// Deref the Chain
CopyMemory(&Split, pSplit, sizeof(CHAINBLOCK));
// Set faRigthChain
faRightChain = faSplit;
// Get Right Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faRightChain, (LPVOID *)&pRight));
// Zero pRight
ZeroBlock(pRight, sizeof(CHAINBLOCK));
// Both new child chains will be half filled
pLeft->cNodes = pRight->cNodes = BTREE_MIN_CAP;
// Set the Right Chains, left pointer to the middle's right pointer
pRight->faLeftChain = Split.rgNode[BTREE_MIN_CAP].faRightChain;
// Adjust faRightChains Parent
if (pRight->faLeftChain)
{
// Locals
LPCHAINBLOCK pLeftChain;
// Get Left Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pRight->faLeftChain, (LPVOID *)&pLeftChain));
// Validate the Current Parent
Assert(pLeftChain->faParent == pRight->faBlock);
// Validate the index
Assert(pLeftChain->iParent == BTREE_MIN_CAP);
// Reset the Parent Index
pLeftChain->iParent = 0;
}
// Set Left Node Count
pRight->cLeftNodes = Split.rgNode[BTREE_MIN_CAP].cRightNodes;
// Set the left chains left chain address to the left chain left chain address
pLeft->faLeftChain = Split.faLeftChain;
// Adjust Parents
if (pLeft->faLeftChain)
{
// Locals
LPCHAINBLOCK pLeftChain;
// Get Left Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pLeft->faLeftChain, (LPVOID *)&pLeftChain));
// Validate the index
Assert(pLeftChain->iParent == 0);
// Reset faParent
pLeftChain->faParent = pLeft->faBlock;
}
// Set Left Nodes
pLeft->cLeftNodes = Split.cLeftNodes;
// Initialize cRightNodes
cRightNodes = (BTREE_MIN_CAP + pRight->cLeftNodes);
// Initialize cLeftNodes
cLeftNodes = (BTREE_MIN_CAP + pLeft->cLeftNodes);
// This splits the chain
for (i=0; i<BTREE_MIN_CAP; i++)
{
// Copy Right Node
CopyMemory(&pRight->rgNode[i], &Split.rgNode[i + BTREE_MIN_CAP + 1], sizeof(CHAINNODE));
// Adjust the Child's iParent ?
if (pRight->rgNode[i].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get Left Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pRight->rgNode[i].faRightChain, (LPVOID *)&pRightChain));
// Validate the Current Parent
Assert(pRightChain->faParent == pRight->faBlock);
// Validate the index
Assert(pRightChain->iParent == i + BTREE_MIN_CAP + 1);
// Reset the Parent
pRightChain->iParent = i;
}
// Count All Child Nodes on the Right
cRightNodes += pRight->rgNode[i].cRightNodes;
// Copy Left Node
CopyMemory(&pLeft->rgNode[i], &Split.rgNode[i], sizeof(CHAINNODE));
// If there is a right chain
if (pLeft->rgNode[i].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get Left Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pLeft->rgNode[i].faRightChain, (LPVOID *)&pRightChain));
// Validate the Old Parent
Assert(pRightChain->faParent == Split.faBlock);
// Validate the index
Assert(pRightChain->iParent == i);
// Reset the Parent
pRightChain->faParent = pLeft->faBlock;
}
// Count All Child Nodes on the Left
cLeftNodes += pLeft->rgNode[i].cRightNodes;
}
// Set the middle nodes right chain address to the right chain's start address
Split.rgNode[BTREE_MIN_CAP].faRightChain = faRightChain;
// Set Right Nodes
Split.rgNode[BTREE_MIN_CAP].cRightNodes = cRightNodes;
// Done with pLeft and pRight
pLeft = pRight = NULL;
// Elevate the middle leaf node - Create new root chain, then were done
if (0 == Split.faParent)
{
// Allocate Space for this chain
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_CHAIN, 0, (LPVOID *)&pParent));
// ZeroInit
ZeroBlock(pParent, sizeof(CHAINBLOCK));
// Set Node count
pParent->cNodes = 1;
// Set Left Chain
pParent->faLeftChain = faLeftChain;
// Set Left Node Count
pParent->cLeftNodes = cLeftNodes;
// Copy the Root Node
CopyMemory(&pParent->rgNode[0], &Split.rgNode[BTREE_MIN_CAP], sizeof(CHAINNODE));
// New Root Chain Address
m_pHeader->rgfaIndex[iIndex] = pParent->faBlock;
// Get pLeft
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faLeftChain, (LPVOID *)&pLeft));
// Get Right
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faRightChain, (LPVOID *)&pRight));
// Set faLeft's faParent
pRight->faParent = pLeft->faParent = pParent->faBlock;
// Set faLeft's iParent
pRight->iParent = pLeft->iParent = 0;
}
// Otherwise, locate chainNodeMiddle's parent chain list!
else
{
// De-Reference
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, Split.faParent, (LPVOID *)&pParent));
// Insert leaf's middle record into the parent
IF_FAILEXIT(hr = _ChainInsert(iIndex, pParent, &Split.rgNode[BTREE_MIN_CAP], &iNode));
// Get pLeft
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faLeftChain, (LPVOID *)&pLeft));
// Get Right
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faRightChain, (LPVOID *)&pRight));
// Set faLeft's faParent
pRight->faParent = pLeft->faParent = pParent->faBlock;
// Update Surrounding Nodes
if (iNode > 0)
{
// Set faRight Chain
pParent->rgNode[iNode - 1].faRightChain = faLeftChain;
// Set cRightNodes
pParent->rgNode[iNode - 1].cRightNodes = cLeftNodes;
// Set faLeft's iParent
pRight->iParent = (BYTE)iNode;
// Set Left Parent
pLeft->iParent = iNode - 1;
}
// iNode is first node
else if (iNode == 0)
{
// Set faLeftChain
pParent->faLeftChain = faLeftChain;
// Set cLeftNodes
pParent->cLeftNodes = cLeftNodes;
// Set faLeft's iParent
pRight->iParent = pLeft->iParent = 0;
}
// If Node is FULL, we must do a split insert
if (pParent->cNodes > BTREE_ORDER)
{
// Recursive Split
IF_FAILEXIT(hr = _SplitChainInsert(iIndex, Split.faParent));
}
// Other wise, simply write the updated chain list
else
{
// Increment Parent Record Count
IF_FAILEXIT(hr = _AdjustParentNodeCount(iIndex, Split.faParent, 1));
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FreeRecordStorage
//--------------------------------------------------------------------------
HRESULT CDatabase::_FreeRecordStorage(OPERATIONTYPE tyOperation,
FILEADDRESS faRecord)
{
// Locals
HRESULT hr=S_OK;
ULONG i;
LPVOID pBinding=NULL;
LPCTABLECOLUMN pColumn;
// Trace
TraceCall("CDatabase::_FreeRecordStorage");
// Invalid Args
Assert(faRecord > 0);
// Does the record have streams
if (OPERATION_DELETE == tyOperation && ISFLAGSET(m_pSchema->dwFlags, TSF_HASSTREAMS))
{
// Allocate a Record
IF_NULLEXIT(pBinding = PHeapAllocate(HEAP_ZERO_MEMORY, m_pSchema->cbBinding));
// Load the Record from the file
IF_FAILEXIT(hr = _ReadRecord(faRecord, pBinding));
// Walk through the members in the structure
for (i=0; i<m_pSchema->cColumns; i++)
{
// Readability
pColumn = &m_pSchema->prgColumn[i];
// Variable Length Member ?
if (CDT_STREAM == pColumn->type)
{
// Get the Starting address of the stream
FILEADDRESS faStart = *((FILEADDRESS *)((LPBYTE)pBinding + pColumn->ofBinding));
// Release Stream Storage...
if (faStart > 0)
{
// Delete the Stream
DeleteStream(faStart);
}
}
}
}
// Free the base record
IF_FAILEXIT(hr = _FreeBlock(BLOCK_RECORD, faRecord));
exit:
// Cleanup
SafeFreeBinding(pBinding);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::DeleteStream
//--------------------------------------------------------------------------
HRESULT CDatabase::DeleteStream(FILEADDRESS faStart)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
HLOCK hLock=NULL;
BOOL fFound=FALSE;
// Trace
TraceCall("CDatabase::DeleteStream");
// See if this stream is currently open any where...
if (0 == faStart)
return TraceResult(E_INVALIDARG);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Look for faStart in the stream table
for (i=0; i<CMAX_OPEN_STREAMS; i++)
{
// Is this it ?
if (faStart == m_pShare->rgStream[i].faStart)
{
// The Stream Must be Open
Assert(m_pShare->rgStream[i].cOpenCount > 0);
// Mark the stream for delete on close
m_pShare->rgStream[i].fDeleteOnClose = TRUE;
// Not that I found It
fFound = TRUE;
// Done
break;
}
}
// If we didn't find it, then lets free the storage
if (FALSE == fFound)
{
// Free the Stream Storage
IF_FAILEXIT(hr = _FreeStreamStorage(faStart));
}
// Update the Version
m_pShare->dwVersion++;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_StreamSychronize
//--------------------------------------------------------------------------
HRESULT CDatabase::_StreamSychronize(CDatabaseStream *pStream)
{
// Locals
HRESULT hr=S_OK;
LPOPENSTREAM pInfo;
LPSTREAMBLOCK pBlock;
DWORD iBlock=0;
BOOL fFound=FALSE;
IF_DEBUG(DWORD cbOffset=0;)
FILEADDRESS faCurrent;
// Trace
TraceCall("CDatabase::_StreamSychronize");
// Invalid Args
Assert(pStream);
// Get Stream Info
pInfo = &m_pShare->rgStream[pStream->m_iStream];
// Validate
if (pInfo->faStart == pStream->m_faStart)
goto exit;
// Set faCurrent
faCurrent = pInfo->faStart;
// Loop until I find pStream->m_iCurrent
while (faCurrent > 0)
{
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, faCurrent, (LPVOID *)&pBlock));
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
// Is this It ?
if (iBlock == pStream->m_iCurrent)
{
// We Found It
fFound = TRUE;
// Save m_faCurrent
pStream->m_faCurrent = faCurrent;
// Validate Size
Assert(pStream->m_cbCurrent <= pBlock->cbData && cbOffset + pStream->m_cbCurrent == pStream->m_cbOffset);
// Done
break;
}
// Goto Next
faCurrent = pBlock->faNext;
// Increment iBlock
iBlock++;
// Increment cbOffset
IF_DEBUG(cbOffset += pBlock->cbData;)
}
// If not found...
if (FALSE == fFound)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Reset Start Address
pStream->m_faStart = pInfo->faStart;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::StreamCompareDatabase
//--------------------------------------------------------------------------
HRESULT CDatabase::StreamCompareDatabase(CDatabaseStream *pStream,
IDatabase *pDatabase)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLockSrc=NULL;
HLOCK hLockDst=NULL;
CDatabase *pDB=NULL;
// Lock
IF_FAILEXIT(hr = Lock(&hLockSrc));
// Lock the Dst
IF_FAILEXIT(hr = pDatabase->Lock(&hLockDst));
// QI for CDatabase
IF_FAILEXIT(hr = pDatabase->QueryInterface(IID_CDatabase, (LPVOID *)&pDB));
// Compare m_pStorage->pszMap
hr = (0 == StrCmpIW(m_pStorage->pszMap, pDB->m_pStorage->pszMap)) ? S_OK : S_FALSE;
exit:
// Cleanup
SafeRelease(pDB);
// Mutal Exclusion
Unlock(&hLockSrc);
pDatabase->Unlock(&hLockDst);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetStreamAddress
//--------------------------------------------------------------------------
HRESULT CDatabase::GetStreamAddress(CDatabaseStream *pStream,
LPFILEADDRESS pfaStream)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// StreamSychronize
IF_FAILEXIT(hr = _StreamSychronize(pStream));
// Get the address
*pfaStream = pStream->m_faStart;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::StreamRead
//--------------------------------------------------------------------------
HRESULT CDatabase::StreamRead(CDatabaseStream *pStream, LPVOID pvData,
ULONG cbWanted, ULONG *pcbRead)
{
// Locals
HRESULT hr=S_OK;
LPBYTE pbMap;
DWORD cbRead=0;
DWORD cbGet;
LPSTREAMBLOCK pBlock;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::_StreamRead");
// Init pcbRead
if (pcbRead)
*pcbRead = 0;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Invalid Args
Assert(pStream && pvData);
// StreamSychronize
IF_FAILEXIT(hr = _StreamSychronize(pStream));
// Loop and Read
while (cbRead < cbWanted)
{
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, pStream->m_faCurrent, (LPVOID *)&pBlock));
// Time to go to the next block ?
if (pStream->m_cbCurrent == pBlock->cbData && 0 != pBlock->faNext)
{
// Set m_faCurrent
pStream->m_faCurrent = pBlock->faNext;
// Increment m_iCurrent
pStream->m_iCurrent++;
// Reset offset into current block
pStream->m_cbCurrent = 0;
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, pStream->m_faCurrent, (LPVOID *)&pBlock));
}
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
// Validate the Offset
Assert(pStream->m_cbCurrent <= pBlock->cbData);
// Determine how much we can read from the current block ?
cbGet = min(cbWanted - cbRead, pBlock->cbData - pStream->m_cbCurrent);
// Nothing left to get
if (cbGet == 0)
break;
// Read some bytes
pbMap = ((LPBYTE)pBlock + sizeof(STREAMBLOCK));
// Copy the Data
CopyMemory((LPBYTE)pvData + cbRead, pbMap + pStream->m_cbCurrent, cbGet);
// Increment Amount of Data Read
cbRead += cbGet;
// Increment Offset within Current Block
pStream->m_cbCurrent += cbGet;
// Global Offset
pStream->m_cbOffset += cbGet;
}
// Init pcbRead
if (pcbRead)
*pcbRead = cbRead;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::StreamWrite
//--------------------------------------------------------------------------
HRESULT CDatabase::StreamWrite(CDatabaseStream *pStream, const void *pvData,
ULONG cb, ULONG *pcbWrote)
{
// Locals
HRESULT hr=S_OK;
LPBYTE pbMap;
DWORD cbWrote=0;
DWORD cbPut;
LPSTREAMBLOCK pBlock;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::StreamWrite");
// Init pcbRead
if (pcbWrote)
*pcbWrote = 0;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Invalid Args
Assert(pStream && pStream->m_tyAccess == ACCESS_WRITE && pvData);
// StreamSychronize
IF_FAILEXIT(hr = _StreamSychronize(pStream));
// Loop and Read
while (cbWrote < cb)
{
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, pStream->m_faCurrent, (LPVOID *)&pBlock));
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
// Validation
Assert(pStream->m_cbCurrent <= pBlock->cbData);
// Have we written to the end of the current block and there are more blocks
if (pStream->m_cbCurrent == pBlock->cbSize)
{
// Are there more blocks
if (0 == pBlock->faNext)
{
// Locals
LPSTREAMBLOCK pBlockNew;
// Allocate a block in the tree
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_STREAM, 0, (LPVOID *)&pBlockNew));
// Get the Current Stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, pStream->m_faCurrent, (LPVOID *)&pBlock));
// Set the next block address on the current block
pBlock->faNext = pBlockNew->faBlock;
// Access the block
pBlock = pBlockNew;
// Initial 0 data
pBlock->cbData = 0;
// No next block
pBlock->faNext = 0;
}
// Otherwise, move to the next block
else
{
// Save faBlcok
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, pBlock->faNext, (LPVOID *)&pBlock));
}
// Set m_faCurrent
pStream->m_faCurrent = pBlock->faBlock;
// Increment the block index
pStream->m_iCurrent++;
// Reset the Current Block Offset
pStream->m_cbCurrent = 0;
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
}
// Compute how much of the cb we can write
cbPut = min(cb - cbWrote, pBlock->cbSize - pStream->m_cbCurrent);
// Read some bytes
pbMap = ((LPBYTE)pBlock + sizeof(STREAMBLOCK));
// Check memory
//Assert(FALSE == IsBadWritePtr(pbMap, cbPut));
// Check memory
//Assert(FALSE == IsBadReadPtr((LPBYTE)pvData + cbWrote, cbPut));
// Copy the Data
CopyMemory(pbMap + pStream->m_cbCurrent, (LPBYTE)pvData + cbWrote, cbPut);
// Increment the Offset within the current block
pStream->m_cbCurrent += cbPut;
// Increment the Offset within the current block
pStream->m_cbOffset += cbPut;
// Increment the Amount that has been wrote
cbWrote += cbPut;
// Increment the amount of data in the block only if we are expanding its size
if (0 == pBlock->faNext && pStream->m_cbCurrent > pBlock->cbData)
{
// Set the Amount of Data in this block
pBlock->cbData = pStream->m_cbCurrent;
}
// Otherwise, the block should be full
else
Assert(pBlock->cbData == pBlock->cbSize);
}
// Init pcbRead
if (pcbWrote)
*pcbWrote = cbWrote;
// Update Version
m_pShare->dwVersion++;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::StreamSeek
//--------------------------------------------------------------------------
HRESULT CDatabase::StreamSeek(CDatabaseStream *pStream, LARGE_INTEGER liMove,
DWORD dwOrigin, ULARGE_INTEGER *pulNew)
{
// Locals
HRESULT hr=S_OK;
DWORD cbNewOffset;
LONG lOffset;
DWORD cbSize=0;
FILEADDRESS faBlock;
LPSTREAMBLOCK pBlock;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::StreamSeek");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Invalid Args
Assert(pStream && 0 == liMove.HighPart);
// StreamSychronize
IF_FAILEXIT(hr = _StreamSychronize(pStream));
// Cast lowpart
lOffset = (LONG)liMove.LowPart;
// STREAM_SEEK_CUR
if (STREAM_SEEK_CUR == dwOrigin)
{
// Validate
if (lOffset < 0 && (DWORD)(0 - lOffset) > pStream->m_cbOffset)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Set new Offset...
cbNewOffset = (pStream->m_cbOffset + lOffset);
}
// STREAM_SEEK_END
else if (STREAM_SEEK_END == dwOrigin)
{
// Compute Size...from current offset
faBlock = pStream->m_faCurrent;
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, faBlock, (LPVOID *)&pBlock));
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
// Validation
Assert(pStream->m_cbCurrent <= pBlock->cbData && pStream->m_cbCurrent <= pBlock->cbSize);
// Set cbSize
cbSize = pStream->m_cbOffset + (pBlock->cbData - pStream->m_cbCurrent);
// Goto the next block
faBlock = pBlock->faNext;
// While
while (faBlock > 0)
{
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, faBlock, (LPVOID *)&pBlock));
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
// Increment cbSize
cbSize += pBlock->cbData;
// Set faBlock
faBlock = pBlock->faNext;
}
// If lOffset is negative and absolutely larger than the size of the stream
if (lOffset > 0 || (lOffset < 0 && (DWORD)(0 - lOffset) > cbSize))
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Save new offset
cbNewOffset = cbSize + lOffset;
}
// STREAM_SEEK_SET
else
{
// Can't be negative
if (lOffset < 0)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Save new offset
cbNewOffset = lOffset;
}
// Did the offset change
if (cbNewOffset != pStream->m_cbOffset)
{
// Reset Current Position
pStream->m_faCurrent = pStream->m_faStart;
pStream->m_cbCurrent = 0;
pStream->m_iCurrent = 0;
pStream->m_cbOffset = 0;
// Initialize the loop
faBlock = pStream->m_faStart;
// Lets seek from the start of the stream to the new offset
while (faBlock > 0)
{
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, faBlock, (LPVOID *)&pBlock));
// Validate
Assert(0 == pBlock->faNext ? TRUE : pBlock->cbData == pBlock->cbSize);
// Save some stuff
pStream->m_faCurrent = pBlock->faBlock;
// Is this the block we want ?
if (pStream->m_cbOffset + pBlock->cbData >= cbNewOffset)
{
// Compute m_cbCurrent
pStream->m_cbCurrent = (cbNewOffset - pStream->m_cbOffset);
// Set m_cbOffset
pStream->m_cbOffset += pStream->m_cbCurrent;
// Done
break;
}
// Set m_cbCurrent
pStream->m_cbCurrent = pBlock->cbData;
// Increment global offset
pStream->m_cbOffset += pBlock->cbData;
// Increment Index
pStream->m_iCurrent++;
// Goto Next
faBlock = pBlock->faNext;
}
}
// Return Position
if (pulNew)
pulNew->LowPart = pStream->m_cbOffset;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::StreamGetAddress
//--------------------------------------------------------------------------
HRESULT CDatabase::StreamGetAddress(CDatabaseStream *pStream, LPFILEADDRESS pfaStart)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::StreamGetAddress");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Invalid Args
Assert(pStream);
// StreamSychronize
IF_FAILEXIT(hr = _StreamSychronize(pStream));
// Return the Address
*pfaStart = pStream->m_faStart;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::StreamRelease
//--------------------------------------------------------------------------
HRESULT CDatabase::StreamRelease(CDatabaseStream *pStream)
{
// Locals
HRESULT hr=S_OK;
LPOPENSTREAM pInfo;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::StreamRelease");
// Invalid Args
Assert(pStream);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Validate
Assert(m_pShare->rgStream);
// Cast iStream
pInfo = &m_pShare->rgStream[pStream->m_iStream];
// Better have a reference count
Assert(pInfo->cOpenCount > 0);
// Decrement cOpenCount
pInfo->cOpenCount--;
// Reset the Lock Count based on the access type
if (ACCESS_WRITE == pStream->m_tyAccess)
{
// Validate the lLock
Assert(LOCK_VALUE_WRITER == pInfo->lLock && 0 == pInfo->cOpenCount);
// Set to none
pInfo->lLock = LOCK_VALUE_NONE;
}
// Otherwise, must have been locked for a read
else
{
// Validate
Assert(ACCESS_READ == pStream->m_tyAccess && pInfo->lLock > 0);
// Validate Lock count
pInfo->lLock--;
}
// If this was the last reference...
if (0 == pInfo->cOpenCount)
{
// If the stream is marked for deletion
if (TRUE == pInfo->fDeleteOnClose)
{
// Validate Start Address
Assert(pInfo->faStart > 0);
// Free the Storage
IF_FAILEXIT(hr = _FreeStreamStorage(pInfo->faStart));
}
// Zero Out This Entry
ZeroMemory(pInfo, sizeof(OPENSTREAM));
}
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FreeStreamStorage
//--------------------------------------------------------------------------
HRESULT CDatabase::_FreeStreamStorage(FILEADDRESS faStart)
{
// Locals
HRESULT hr=S_OK;
LPSTREAMBLOCK pBlock;
FILEADDRESS faCurrent;
// Trace
TraceCall("CDatabase::_FreeStreamStorage");
// Invalid Args
Assert(faStart > 0);
// Initialize Loop
faCurrent = faStart;
// Read through all of the blocks (i.e. verify headers and count the number of chains)
while (faCurrent)
{
// Valid stream Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_STREAM, faCurrent, (LPVOID *)&pBlock));
// Set faCurrent
faCurrent = pBlock->faNext;
// Read the header
IF_FAILEXIT(hr = _FreeBlock(BLOCK_STREAM, pBlock->faBlock));
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::CreateStream
//--------------------------------------------------------------------------
HRESULT CDatabase::CreateStream(LPFILEADDRESS pfaStream)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faStart=0;
HLOCK hLock=NULL;
LPSTREAMBLOCK pStream;
// Trace
TraceCall("CDatabase::CreateStream");
// Invalid Arg
Assert(pfaStream);
// Initialize
*pfaStream = NULL;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Allocate the first 512 block of the stream
IF_FAILEXIT(hr = _AllocateBlock(BLOCK_STREAM, 0, (LPVOID *)&pStream));
// Write the Initialize Header
pStream->cbData = 0;
pStream->faNext = 0;
// Return the block
*pfaStream = pStream->faBlock;
// Modify the Version
m_pShare->dwVersion++;
exit:
// Thread Safety
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::CopyStream
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::CopyStream(IDatabase *pDatabase, FILEADDRESS faStream,
LPFILEADDRESS pfaNew)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faNew=0;
DWORD cbRead;
HLOCK hLock=NULL;
BYTE rgbBuffer[4096];
IStream *pStmDst=NULL;
IStream *pStmSrc=NULL;
// Trace
TraceCall("CDatabase::CopyStream");
// Invalid Arg
if (NULL == pDatabase || 0 == faStream || NULL == pfaNew)
return(TraceResult(E_INVALIDARG));
// Initialize
*pfaNew = NULL;
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Allocate a Stream in the Destination Database
IF_FAILEXIT(hr = pDatabase->CreateStream(&faNew));
// Open It Dst
IF_FAILEXIT(hr = pDatabase->OpenStream(ACCESS_WRITE, faNew, &pStmDst));
// Open It Src
IF_FAILEXIT(hr = OpenStream(ACCESS_READ, faStream, &pStmSrc));
// Read and Write...
while (1)
{
// Read a Block From the Source
IF_FAILEXIT(hr = pStmSrc->Read(rgbBuffer, sizeof(rgbBuffer), &cbRead));
// Done
if (0 == cbRead)
break;
// Write It
IF_FAILEXIT(hr = pStmDst->Write(rgbBuffer, cbRead, NULL));
// Yield Compacting
if (m_pShare->fCompacting && m_fCompactYield)
{
// Give up a timeslice
Sleep(0);
}
}
// Comit the Dest
IF_FAILEXIT(hr = pStmDst->Commit(STGC_DEFAULT));
// Modify the Version
*pfaNew = faNew;
// Don't Free It
faNew = 0;
exit:
// Cleanup
SafeRelease(pStmDst);
SafeRelease(pStmSrc);
// Failure
if (faNew)
{
// Delete the Stream
SideAssert(SUCCEEDED(pDatabase->DeleteStream(faNew)));
}
// Thread Safety
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::ChangeStreamLock
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::ChangeStreamLock(IStream *pStream, ACCESSTYPE tyAccessNew)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
LPOPENSTREAM pInfo;
CDatabaseStream *pDBStream=NULL;
// Trace
TraceCall("CDatabase::ChangeStreamLock");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Get Private Stream
IF_FAILEXIT(hr = pStream->QueryInterface(IID_CDatabaseStream, (LPVOID *)&pDBStream));
// Get Stream Info
pInfo = &m_pShare->rgStream[pDBStream->m_iStream];
// Going to Writer
if (ACCESS_WRITE == tyAccessNew)
{
// Already Locked for Write
if (LOCK_VALUE_WRITER == pInfo->lLock)
{
Assert(ACCESS_WRITE == pDBStream->m_tyAccess);
goto exit;
}
// If more than one reader
if (pInfo->lLock > 1)
{
hr = TraceResult(DB_E_LOCKEDFORREAD);
goto exit;
}
// Change Lock Type
pInfo->lLock = LOCK_VALUE_WRITER;
// Write Access
pDBStream->m_tyAccess = ACCESS_WRITE;
}
// Otherwise, change to read...
else
{
// Validate
Assert(ACCESS_READ == tyAccessNew);
// If already locked for read
if (LOCK_VALUE_WRITER != pInfo->lLock)
{
Assert(ACCESS_READ == pDBStream->m_tyAccess);
goto exit;
}
// Change to 1 reader
pInfo->lLock = 1;
// Read Access
pDBStream->m_tyAccess = ACCESS_READ;
}
exit:
// Mutal Exclusion
Unlock(&hLock);
// Cleanup
SafeRelease(pDBStream);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::OpenStream
//--------------------------------------------------------------------------
HRESULT CDatabase::OpenStream(ACCESSTYPE tyAccess, FILEADDRESS faStart,
IStream **ppStream)
{
// Locals
HRESULT hr=S_OK;
STREAMINDEX i;
STREAMINDEX iStream=INVALID_STREAMINDEX;
STREAMINDEX iFirstUnused=INVALID_STREAMINDEX;
LPOPENSTREAM pInfo;
HLOCK hLock=NULL;
CDatabaseStream *pStream=NULL;
// Trace
TraceCall("CDatabase::OpenStream");
// Invalid Arg
if (0 == faStart || NULL == ppStream)
return TraceResult(E_INVALIDARG);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Validate
Assert(m_pShare->rgStream);
// Does the faStart Stream Exist in my stream table
for (i=0; i<CMAX_OPEN_STREAMS; i++)
{
// Is this the stream
if (faStart == m_pShare->rgStream[i].faStart)
{
// This must already be locked for write or read
Assert(LOCK_VALUE_WRITER == m_pShare->rgStream[i].lLock || m_pShare->rgStream[i].lLock > 0);
// Get Access
if (ACCESS_WRITE == tyAccess)
{
hr = TraceResult(DB_E_LOCKEDFORREAD);
goto exit;
}
// Otheriwise, get read lock
else
{
// If Locked for a write
if (LOCK_VALUE_WRITER == m_pShare->rgStream[i].lLock)
{
hr = TraceResult(DB_E_LOCKEDFORWRITE);
goto exit;
}
}
// Set iStream
iStream = i;
// Increment Open Count for this stream
m_pShare->rgStream[i].cOpenCount++;
// I Must have got a read lock
Assert(ACCESS_READ == tyAccess && m_pShare->rgStream[i].lLock > 0);
// Increment Reader Count
m_pShare->rgStream[i].lLock++;
}
// If this entry is unused, lets remember it
if (FALSE == m_pShare->rgStream[i].fInUse && INVALID_STREAMINDEX == iFirstUnused)
iFirstUnused = i;
}
// If we didn't find faStart in the stream table, append an entry ?
if (INVALID_STREAMINDEX == iStream)
{
// Is there enought space
if (INVALID_STREAMINDEX == iFirstUnused)
{
hr = TraceResult(DB_E_STREAMTABLEFULL);
goto exit;
}
// Set iStream
iStream = iFirstUnused;
// Readability
pInfo = &m_pShare->rgStream[iStream];
// This entry is now in use
pInfo->fInUse = TRUE;
// Register the starting address of this stream
pInfo->faStart = faStart;
// Reader or Writer ?
pInfo->lLock = (ACCESS_WRITE == tyAccess) ? LOCK_VALUE_WRITER : (m_pShare->rgStream[i].lLock + 1);
// Set Open count
pInfo->cOpenCount++;
}
// Allocate an Object Database Stream
IF_NULLEXIT(pStream = new CDatabaseStream(this, iStream, tyAccess, faStart));
// Return
*ppStream = (IStream *)pStream;
pStream = NULL;
exit:
// Mutal Exclusion
Unlock(&hLock);
// Cleanup
SafeRelease(pStream);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CollapseChain
//--------------------------------------------------------------------------
HRESULT CDatabase::_CollapseChain(LPCHAINBLOCK pChain, NODEINDEX iDelete)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
// Trace
TraceCall("CDatabase::_CollapseChain");
// Simply set node[i] = node[i+1]; cNodes -= 1; Write the Chain !
for (i=iDelete; i<pChain->cNodes - 1; i++)
{
// Copy i + 1 chain node down one
CopyMemory(&pChain->rgNode[i], &pChain->rgNode[i + 1], sizeof(CHAINNODE));
// If there is a right chain
if (pChain->rgNode[i].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get Right Chain block
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pChain->rgNode[i].faRightChain, (LPVOID *)&pRightChain));
// Validate the current Parent
Assert(pRightChain->faParent == pChain->faBlock);
// Validate the current index
Assert(pRightChain->iParent == i + 1);
// Reset the Parent
pRightChain->iParent = i;
}
}
// Decrement count
pChain->cNodes--;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_IndexDeleteRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_IndexDeleteRecord(INDEXORDINAL iIndex,
FILEADDRESS faDelete, NODEINDEX iDelete)
{
// Locals
HRESULT hr=S_OK;
HRESULT hrIsLeafChain;
NODEINDEX i;
LPCHAINBLOCK pDelete;
CHAINSHARETYPE tyShare;
CHAINDELETETYPE tyDelete;
FILEADDRESS faShare;
LPCHAINBLOCK pSuccessor;
FILEADDRESS faRecord;
// Trace
TraceCall("CDatabase::_IndexDeleteRecord");
// Invalid Args
Assert(iDelete < BTREE_ORDER);
// Get Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faDelete, (LPVOID *)&pDelete));
// Is this a leaf node
hrIsLeafChain = _IsLeafChain(pDelete);
// Case 1: Deleting a leaf node that does not violate the minimum capcity constraint
if (S_OK == hrIsLeafChain && (pDelete->cNodes - 1 >= BTREE_MIN_CAP || 0 == pDelete->faParent))
{
// Collapse the Chain
_CollapseChain(pDelete, iDelete);
// Update the Parent Node Count
IF_FAILEXIT(hr = _AdjustParentNodeCount(iIndex, faDelete, -1));
// Did we just delete the root chain
if (0 == pDelete->faParent && 0 == pDelete->cNodes)
{
// Add pShare to free list
IF_FAILEXIT(hr = _FreeBlock(BLOCK_CHAIN, faDelete));
// Update the header, we don't have any more nodes
m_pHeader->rgfaIndex[iIndex] = 0;
}
}
// Case 2: Deleting from a nonleaf node and replacing that record with a record from a leaf node that does not violate the minimum capacity contstraint.
else if (S_FALSE == hrIsLeafChain)
{
// Get Inorder Successor
IF_FAILEXIT(hr = _GetInOrderSuccessor(faDelete, iDelete, &pSuccessor));
// Free Tree Block
faRecord = pDelete->rgNode[iDelete].faRecord;
// Free Tree Block
pDelete->rgNode[iDelete].faRecord = pSuccessor->rgNode[0].faRecord;
// Delete pSuccessor->rgNode[0] - and the record which we just replaced
pSuccessor->rgNode[0].faRecord = faRecord;
// Delete this node
IF_FAILEXIT(hr = _IndexDeleteRecord(iIndex, pSuccessor->faBlock, 0));
}
// Case 3: Deleting from a leaf node that causes a minimum capacity constraint violation that can be corrected by redistributing the records with an adjacent sibling node.
else
{
// Decide if I need to do a shared or coalesce type delete
IF_FAILEXIT(hr = _DecideHowToDelete(&faShare, faDelete, &tyDelete, &tyShare));
// Collapse the Chain
_CollapseChain(pDelete, iDelete);
// If NULL, then do a coalesc
if (CHAIN_DELETE_SHARE == tyDelete)
{
// Adjust the Parent's Parents
IF_FAILEXIT(hr = _AdjustParentNodeCount(iIndex, faDelete, -1));
// Do a shared deleted
IF_FAILEXIT(hr = _ChainDeleteShare(iIndex, faDelete, faShare, tyShare));
}
// Coalesce Type Delete
else
{
// Validate the delete type
Assert(faShare && CHAIN_DELETE_COALESCE == tyDelete && pDelete->faParent != 0);
// Adjust the Parent's Parents
IF_FAILEXIT(hr = _AdjustParentNodeCount(iIndex, pDelete->faParent, -1));
// Do a coalescing delete
IF_FAILEXIT(hr = _ChainDeleteCoalesce(iIndex, faDelete, faShare, tyShare));
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_IsLeafChain
//--------------------------------------------------------------------------
HRESULT CDatabase::_IsLeafChain(LPCHAINBLOCK pChain)
{
// If Left Chain is NULL, then all chains must be null
return (0 == pChain->faLeftChain) ? S_OK : S_FALSE;
}
//--------------------------------------------------------------------------
// CDatabase::_ChainDeleteShare
//--------------------------------------------------------------------------
HRESULT CDatabase::_ChainDeleteShare(INDEXORDINAL iIndex,
FILEADDRESS faDelete, FILEADDRESS faShare, CHAINSHARETYPE tyShare)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
NODEINDEX iInsert;
NODEINDEX iParent;
FILEADDRESS faParentRightChain;
DWORD cParentRightNodes;
DWORD cCopyNodes;
LPCHAINBLOCK pDelete;
LPCHAINBLOCK pShare;
LPCHAINBLOCK pParent;
// Trace
TraceCall("CDatabase::_ChainDeleteShare");
// Invalid ARgs
Assert(faDelete && faShare);
// Get pShare
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faShare, (LPVOID *)&pShare));
// Get the Parent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pShare->faParent, (LPVOID *)&pParent));
// Get pDelete
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faDelete, (LPVOID *)&pDelete));
// Validation
Assert(pShare->faParent == pDelete->faParent);
// Setup iParent
iParent = (CHAIN_SHARE_LEFT == tyShare) ? pDelete->iParent : pShare->iParent;
// Save Paren't Right Chain, we are going to replace iParent with the last left or first right node
faParentRightChain = pParent->rgNode[iParent].faRightChain;
// Save the cParentRightNodes
cParentRightNodes = pParent->rgNode[iParent].cRightNodes;
// Insert Parent Node into lpChainFound - Parent Pointers stay the same
pParent->rgNode[iParent].faRightChain = 0;
// Reset cRightNodes
pParent->rgNode[iParent].cRightNodes = 0;
// Insert the parent node into the chain that we are deleting from
IF_FAILEXIT(hr = _ChainInsert(iIndex, pDelete, &pParent->rgNode[iParent], &iInsert));
// If promoting from the Left, promote Node: cNodes-1 to parent
if (CHAIN_SHARE_LEFT == tyShare)
{
// Should have inserted at position zero
Assert(0 == iInsert);
// Promote Node: 0 to from the deletion node into the parent node
pDelete->rgNode[0].faRightChain = pDelete->faLeftChain;
// Propagate cLeftNodes to cRightNodes
pDelete->rgNode[0].cRightNodes = pDelete->cLeftNodes;
// Update Left Chain Address
pDelete->faLeftChain = pShare->rgNode[pShare->cNodes - 1].faRightChain;
// Update the left chain's parent
if (pDelete->faLeftChain)
{
// Locals
LPCHAINBLOCK pLeftChain;
// Get Left
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pDelete->faLeftChain, (LPVOID *)&pLeftChain));
// Set faParent
pLeftChain->faParent = pDelete->faBlock;
// Set iParent
pLeftChain->iParent = 0;
}
// Update Left Chain Node count
pDelete->cLeftNodes = pShare->rgNode[pShare->cNodes - 1].cRightNodes;
// Save cCopyNodes
cCopyNodes = pDelete->cLeftNodes + 1;
// Copy the node from the left share chain into the parent's spot
CopyMemory(&pParent->rgNode[iParent], &pShare->rgNode[pShare->cNodes - 1], sizeof(CHAINNODE));
// Reset the right chain on the parent
pParent->rgNode[iParent].faRightChain = faParentRightChain;
// Reset the right node count on the parent
pParent->rgNode[iParent].cRightNodes = cParentRightNodes;
// Decrement number of nodes in the share chain
pShare->cNodes--;
// Special case, pShare is to the left of the first node of the parent chain
if (0 == iParent)
{
// Can not be the left chain, otherwise, we wouldn't be sharing from the right
Assert(pShare->faBlock == pParent->faLeftChain && pParent->cLeftNodes > cCopyNodes);
// Decrement Right Node Count
pParent->cLeftNodes -= cCopyNodes;
// Increment
pParent->rgNode[0].cRightNodes += cCopyNodes;
}
// Otherwise, Decrement cRightNodes
else
{
// Validate share left chain
Assert(pShare->faBlock == pParent->rgNode[iParent - 1].faRightChain && pParent->rgNode[iParent - 1].cRightNodes > cCopyNodes);
// Decrement Right Nodes Count
pParent->rgNode[iParent - 1].cRightNodes -= cCopyNodes;
// Validate Right Chain
Assert(pParent->rgNode[iParent].faRightChain == pDelete->faBlock && iParent == pDelete->iParent && pDelete->iParent < pParent->cNodes);
// Increment Right Nodes Count
pParent->rgNode[iParent].cRightNodes += cCopyNodes;
}
}
// Otherwise, share from the right
else
{
// Verify the share type
Assert(CHAIN_SHARE_RIGHT == tyShare && pDelete->cNodes - 1 == iInsert);
// Promote Node: 0 to parent
pDelete->rgNode[pDelete->cNodes - 1].faRightChain = pShare->faLeftChain;
// Update the Right Chain's Parent
if (pDelete->rgNode[pDelete->cNodes - 1].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get Right Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pDelete->rgNode[pDelete->cNodes - 1].faRightChain, (LPVOID *)&pRightChain));
// Set faParent
pRightChain->faParent = pDelete->faBlock;
// Set iParent
pRightChain->iParent = (pDelete->cNodes - 1);
}
// Set cRightNodes Count
pDelete->rgNode[pDelete->cNodes - 1].cRightNodes = pShare->cLeftNodes;
// Save cCopyNodes
cCopyNodes = pDelete->rgNode[pDelete->cNodes - 1].cRightNodes + 1;
// Tree Shift
pShare->faLeftChain = pShare->rgNode[0].faRightChain;
// Tree Shift
pShare->cLeftNodes = pShare->rgNode[0].cRightNodes;
// Copy the node from the share chain to the parent
CopyMemory(&pParent->rgNode[iParent], &pShare->rgNode[0], sizeof(CHAINNODE));
// Collapse this Chain
_CollapseChain(pShare, 0);
// Reset the right chain on the parent
pParent->rgNode[iParent].faRightChain = faParentRightChain;
// Reset the right node count on the parent
pParent->rgNode[iParent].cRightNodes = cParentRightNodes;
// Special case, pShare is to the left of the first node of the parent chain
if (0 == iParent)
{
// Can not be the left chain, otherwise, we wouldn't be sharing from the right
Assert(pParent->rgNode[0].faRightChain == pShare->faBlock && pParent->rgNode[0].cRightNodes > cCopyNodes);
// Decrement Right Node Count
pParent->rgNode[0].cRightNodes -= cCopyNodes;
// Validate
Assert(pParent->faLeftChain == pDelete->faBlock);
// Increment
pParent->cLeftNodes += cCopyNodes;
}
// Otherwise, Decrement cRightNodes
else
{
// Validate share left chain
Assert(pShare->faBlock == pParent->rgNode[iParent].faRightChain && pParent->rgNode[iParent].cRightNodes > 0);
// Decrement Right Node Count
pParent->rgNode[iParent].cRightNodes -= cCopyNodes;
// Validate
Assert(pParent->rgNode[iParent - 1].faRightChain == pDelete->faBlock);
// Increment Left Sibling
pParent->rgNode[iParent - 1].cRightNodes += cCopyNodes;
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_ChainDeleteCoalesce
//--------------------------------------------------------------------------
HRESULT CDatabase::_ChainDeleteCoalesce(INDEXORDINAL iIndex,
FILEADDRESS faDelete, FILEADDRESS faShare, CHAINSHARETYPE tyShare)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
NODEINDEX iInsert;
NODEINDEX iParent;
LPCHAINBLOCK pParent;
LPCHAINBLOCK pDelete;
LPCHAINBLOCK pShare;
FILEADDRESS faShareAgain;
CHAINDELETETYPE tyDelete;
DWORD cRightNodes;
// Trace
TraceCall("CDatabase::_ChainDeleteCoalesce");
// Invalid ARgs
Assert(faDelete && faShare);
// Get pShare
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faShare, (LPVOID *)&pShare));
// Get the Parent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pShare->faParent, (LPVOID *)&pParent));
// Get pDelete
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faDelete, (LPVOID *)&pDelete));
// Validation
Assert(pShare->faParent == pDelete->faParent);
// Setup iParent
iParent = (CHAIN_SHARE_LEFT == tyShare) ? pDelete->iParent : pShare->iParent;
// Insert the Parent
IF_FAILEXIT(hr = _ChainInsert(iIndex, pDelete, &pParent->rgNode[iParent], &iInsert));
// Set newly inserted nodes pointers
if (CHAIN_SHARE_LEFT == tyShare)
{
// Validate
Assert(0 == iInsert);
// Adjust the right Chain
pDelete->rgNode[0].faRightChain = pDelete->faLeftChain;
// Adjust the right node count
pDelete->rgNode[0].cRightNodes = pDelete->cLeftNodes;
// Adjust the left chain
pDelete->faLeftChain = pShare->faLeftChain;
// Update faLeftChain
if (pDelete->faLeftChain)
{
// Locals
LPCHAINBLOCK pLeftChain;
// Get Block
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pDelete->faLeftChain, (LPVOID *)&pLeftChain));
// Set faParent
pLeftChain->faParent = pDelete->faBlock;
// Set iParent
pLeftChain->iParent = 0;
}
// Adjust the left chain node count
pDelete->cLeftNodes = pShare->cLeftNodes;
}
// Share from the right
else
{
// Verify Share Type
Assert(CHAIN_SHARE_RIGHT == tyShare && pDelete->cNodes - 1 == iInsert);
// Adjust the right chain
pDelete->rgNode[pDelete->cNodes - 1].faRightChain = pShare->faLeftChain;
// Update the Right Chain's Parent
if (pDelete->rgNode[pDelete->cNodes - 1].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get the right chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pDelete->rgNode[pDelete->cNodes - 1].faRightChain, (LPVOID *)&pRightChain));
// Set faParent
pRightChain->faParent = pDelete->faBlock;
// Set iParent
pRightChain->iParent = (pDelete->cNodes - 1);
}
// Adjust the right Node Count
pDelete->rgNode[pDelete->cNodes - 1].cRightNodes = pShare->cLeftNodes;
}
// Combine pShare Nodes into pDelete
for (i=0; i<pShare->cNodes; i++)
{
// Insert the Share
IF_FAILEXIT(hr = _ChainInsert(iIndex, pDelete, &pShare->rgNode[i], &iInsert));
// Need to update...
if (pDelete->rgNode[iInsert].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get Right Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pDelete->rgNode[iInsert].faRightChain, (LPVOID *)&pRightChain));
// Set faParent
pRightChain->faParent = pDelete->faBlock;
// Set iParent
pRightChain->iParent = iInsert;
}
}
// Don't use pShare any more
pShare = NULL;
// We can't possible need pShare anymore since we just copied all of its nodes into pDelete
IF_FAILEXIT(hr = _FreeBlock(BLOCK_CHAIN, faShare));
// Collapse the Parent chain
_CollapseChain(pParent, iParent);
// If Parent is less than zero, then lets hope that it was the root node!
if (pParent->cNodes == 0)
{
// This is a bug
Assert(0 == pParent->faParent && m_pHeader->rgfaIndex[iIndex] == pParent->faBlock);
// Add pParent to free list
IF_FAILEXIT(hr = _FreeBlock(BLOCK_CHAIN, pParent->faBlock));
// Kill faParent Link
pDelete->faParent = pDelete->iParent = 0;
// We have a new root chain
m_pHeader->rgfaIndex[iIndex] = pDelete->faBlock;
// No more parent
goto exit;
}
// Compute cRightNodes
cRightNodes = pDelete->cNodes + pDelete->cLeftNodes;
// Loop and count all children
for (i=0; i<pDelete->cNodes; i++)
{
// Increment Node Count
cRightNodes += pDelete->rgNode[i].cRightNodes;
}
// Reset new parent to found node
if (CHAIN_SHARE_LEFT == tyShare)
{
// Readjust new parent node of coalesced chain
if (iParent > pParent->cNodes - 1)
{
// What is happening here
iParent = pParent->cNodes - 1;
}
// We should be replace pShare
Assert(pParent->rgNode[iParent].faRightChain == faShare);
// Update Parent for pDelete
pParent->rgNode[iParent].faRightChain = pDelete->faBlock;
// Adjust Right Chain's Parent
if (pParent->rgNode[iParent].faRightChain)
{
// Locals
LPCHAINBLOCK pRightChain;
// Get Right Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pParent->rgNode[iParent].faRightChain, (LPVOID *)&pRightChain));
// Set faParent
Assert(pRightChain->faParent == pParent->faBlock);
// Set the Parent Index
pRightChain->iParent = iParent;
}
// Compute cRightNodes
pParent->rgNode[iParent].cRightNodes = cRightNodes;
}
// Otherwise, adjust for CHAIN_SHARE_RIGHT
else
{
// Validation
Assert(pDelete->faParent == pParent->faBlock);
// First Node
if (0 == iParent)
{
// Must be left chain
Assert(pParent->faLeftChain == pDelete->faBlock);
// Validate my Parent
Assert(pDelete->iParent == 0);
// Set Left Node Count
pParent->cLeftNodes = cRightNodes;
}
// Otherwise
else
{
// Validate iParent
Assert(pParent->rgNode[iParent - 1].faRightChain == pDelete->faBlock);
// Validation
Assert(pDelete->iParent == iParent - 1);
// Set cRightNodes
pParent->rgNode[iParent - 1].cRightNodes = cRightNodes;
}
}
// Move up the chain, until lpChainPrev == NULL, lpChainPrev->cNodes can not be less than /2
if (0 == pParent->faParent)
goto exit;
// Min capacity
if (pParent->cNodes < BTREE_MIN_CAP)
{
// Decide if I need to do a shared or coalesce type delete
IF_FAILEXIT(hr = _DecideHowToDelete(&faShareAgain, pParent->faBlock, &tyDelete, &tyShare));
// Can't Share, we must coalesc again
if (CHAIN_DELETE_SHARE == tyDelete)
{
// Do a shared delete
IF_FAILEXIT(hr = _ChainDeleteShare(iIndex, pParent->faBlock, faShareAgain, tyShare));
}
// Coalesce type delete
else
{
// Validate
Assert(faShareAgain && CHAIN_DELETE_COALESCE == tyDelete);
// Recursive Coalescing
IF_FAILEXIT(hr = _ChainDeleteCoalesce(iIndex, pParent->faBlock, faShareAgain, tyShare));
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_DecideHowToDelete
//--------------------------------------------------------------------------
HRESULT CDatabase::_DecideHowToDelete(LPFILEADDRESS pfaShare,
FILEADDRESS faDelete, CHAINDELETETYPE *ptyDelete,
CHAINSHARETYPE *ptyShare)
{
// Locals
HRESULT hr=S_OK;
HRESULT hrRight;
HRESULT hrLeft;
LPCHAINBLOCK pRight=NULL;
LPCHAINBLOCK pLeft=NULL;
// Trace
TraceCall("CDatabase::_DecideHowToDelete");
// Initialize
*pfaShare = NULL;
// Get the right sibling
IF_FAILEXIT(hr = _GetRightSibling(faDelete, &pRight));
// Set hrRight
hrRight = hr;
// Did we get a right parent that has nodes that I can steal from ?
if (DB_S_FOUND == hrRight && pRight->cNodes - 1 >= BTREE_MIN_CAP)
{
// Set Delete Type
*ptyDelete = CHAIN_DELETE_SHARE;
// Set Share Type
*ptyShare = CHAIN_SHARE_RIGHT;
// Set Share Link
*pfaShare = pRight->faBlock;
}
else
{
// Try to get the left sibling
IF_FAILEXIT(hr = _GetLeftSibling(faDelete, &pLeft));
// Set hrRight
hrLeft = hr;
// Did I get a left sibling that has nodes that I can steal from ?
if (DB_S_FOUND == hrLeft && pLeft->cNodes - 1 >= BTREE_MIN_CAP)
{
// Set Delete Type
*ptyDelete = CHAIN_DELETE_SHARE;
// Set Share Type
*ptyShare = CHAIN_SHARE_LEFT;
// Set Share Link
*pfaShare = pLeft->faBlock;
}
}
// Did we find a Share ?
if (0 == *pfaShare)
{
// Were are going to coalesce
*ptyDelete = CHAIN_DELETE_COALESCE;
// Coalesce and share from the right?
if (DB_S_FOUND == hrRight)
{
// Set Share Type
*ptyShare = CHAIN_SHARE_RIGHT;
// Set Share Link
*pfaShare = pRight->faBlock;
}
// Coalesce and share from the left?
else
{
// Validation
Assert(DB_S_FOUND == hrLeft);
// Set Share Type
*ptyShare = CHAIN_SHARE_LEFT;
// Set Share Link
*pfaShare = pLeft->faBlock;
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_GetInOrderSuccessor
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetInOrderSuccessor(FILEADDRESS faStart,
NODEINDEX iDelete, LPCHAINBLOCK *ppSuccessor)
{
// Locals
HRESULT hr=S_OK;
FILEADDRESS faCurrent;
LPCHAINBLOCK pCurrent;
LPCHAINBLOCK pStart;
// Trace
TraceCall("CDatabase::_GetInOrderSuccessor");
// Invalid Args
Assert(ppSuccessor);
// Initialize
*ppSuccessor = NULL;
// Get Start
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faStart, (LPVOID *)&pStart));
// Next Chain Address
faCurrent = pStart->rgNode[iDelete].faRightChain;
// Can't be zero
Assert(faCurrent != 0);
// Go until left chain is -1
while (faCurrent)
{
// Get Current
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faCurrent, (LPVOID *)&pCurrent));
// If leaf node, then return
if (S_OK == _IsLeafChain(pCurrent))
{
// Set Successor
*ppSuccessor = pCurrent;
// Done
goto exit;
}
// Otherwise, goto the left
faCurrent = pCurrent->faLeftChain;
}
// Not Found
hr = TraceResult(E_FAIL);
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_GetLeftSibling
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetLeftSibling(FILEADDRESS faCurrent,
LPCHAINBLOCK *ppSibling)
{
// Locals
HRESULT hr=S_OK;
LPCHAINBLOCK pCurrent;
LPCHAINBLOCK pParent;
// Trace
TraceCall("CDatabase::_GetLeftSibling");
// Invalid Args
Assert(faCurrent && ppSibling);
// Get Current
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faCurrent, (LPVOID *)&pCurrent));
// Get Parent
Assert(pCurrent->faParent);
// Get the Parent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pCurrent->faParent, (LPVOID *)&pParent));
// Validate iparent
Assert(pCurrent->iParent < pParent->cNodes);
// iParent is zero
if (0 == pCurrent->iParent)
{
// If pCurrent is the faRightChain ?
if (pCurrent->faBlock != pParent->rgNode[0].faRightChain)
return(DB_S_NOTFOUND);
// Get the Sibling
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pParent->faLeftChain, (LPVOID *)ppSibling));
// Validate
Assert((*ppSibling)->iParent == 0);
}
// iParent is greater than zero ?
else
{
// Validate
Assert(pParent->rgNode[pCurrent->iParent].faRightChain == pCurrent->faBlock);
// Get the Sibling
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pParent->rgNode[pCurrent->iParent - 1].faRightChain, (LPVOID *)ppSibling));
// Validate
Assert((*ppSibling)->iParent == pCurrent->iParent - 1);
}
// Better have a left sibling
Assert(0 != *ppSibling);
// Found
hr = DB_S_FOUND;
exit:
// Set hr
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_GetRightSibling
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetRightSibling(FILEADDRESS faCurrent,
LPCHAINBLOCK *ppSibling)
{
// Locals
HRESULT hr=S_OK;
LPCHAINBLOCK pParent;
LPCHAINBLOCK pCurrent;
// Trace
TraceCall("CDatabase::_GetRightSibling");
// Invalid Args
Assert(faCurrent && ppSibling);
// Get Current
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faCurrent, (LPVOID *)&pCurrent));
// Get Parent
Assert(pCurrent->faParent);
// Get the Parent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pCurrent->faParent, (LPVOID *)&pParent));
// Validate iparent
Assert(pCurrent->iParent < pParent->cNodes);
// iParent is zero
if (0 == pCurrent->iParent && pCurrent->faBlock == pParent->faLeftChain)
{
// Get the Sibling
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pParent->rgNode[0].faRightChain, (LPVOID *)ppSibling));
// Validate
Assert((*ppSibling)->iParent == 0);
}
// iParent is greater than zero ?
else
{
// No more Right chains
if (pCurrent->iParent + 1 == pParent->cNodes)
return DB_S_NOTFOUND;
// Get the Sibling
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pParent->rgNode[pCurrent->iParent + 1].faRightChain, (LPVOID *)ppSibling));
// Validate
Assert((*ppSibling)->iParent == pCurrent->iParent + 1);
}
// Better have a left sibling
Assert(0 != *ppSibling);
// Found
hr = DB_S_FOUND;
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::GetUserData
//--------------------------------------------------------------------------
HRESULT CDatabase::GetUserData(LPVOID pvUserData,
ULONG cbUserData)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::GetUserData");
// Invalid Args
Assert(pvUserData);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Copy the data
CopyMemory(pvUserData, PUSERDATA(m_pHeader), cbUserData);
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::SetUserData
//--------------------------------------------------------------------------
HRESULT CDatabase::SetUserData(LPVOID pvUserData,
ULONG cbUserData)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
// Trace
TraceCall("CDatabase::SetUserData");
// Invalid Args
Assert(pvUserData);
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// Copy the data
CopyMemory(PUSERDATA(m_pHeader), pvUserData, cbUserData);
exit:
// Mutal Exclusion
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CompactMoveRecordStreams
//--------------------------------------------------------------------------
HRESULT CDatabase::_CompactMoveRecordStreams(CDatabase *pDstDB,
LPVOID pBinding)
{
// Locals
HRESULT hr=S_OK;
COLUMNORDINAL iColumn;
FILEADDRESS faSrcStart;
FILEADDRESS faDstStart;
LPOPENSTREAM pInfo;
DWORD i;
// Trace
TraceCall("CDatabase::_CompactMoveRecordStreams");
// Walk through the format
for (iColumn=0; iColumn<m_pSchema->cColumns; iColumn++)
{
// Is this a stream
if (CDT_STREAM != m_pSchema->prgColumn[iColumn].type)
continue;
// Get the source stream starting address
faSrcStart = *((FILEADDRESS *)((LPBYTE)pBinding + m_pSchema->prgColumn[iColumn].ofBinding));
// Is there a stream
if (0 == faSrcStart)
continue;
// Move the Stream
IF_FAILEXIT(hr = CopyStream((IDatabase *)pDstDB, faSrcStart, &faDstStart));
// Store the stream address in the record
*((FILEADDRESS *)((LPBYTE)pBinding + m_pSchema->prgColumn[iColumn].ofBinding)) = faDstStart;
// Loop through the stream table and adjust the start address of all open streams
for (i=0; i<CMAX_OPEN_STREAMS; i++)
{
// Readability
pInfo = &m_pShare->rgStream[i];
// Is In use...
if (TRUE == pInfo->fInUse && faSrcStart == pInfo->faStart)
{
// Change the Address
pInfo->faMoved = faDstStart;
// Break;
break;
}
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CompactMoveOpenDeletedStreams
//--------------------------------------------------------------------------
HRESULT CDatabase::_CompactMoveOpenDeletedStreams(CDatabase *pDstDB)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
LPOPENSTREAM pInfo;
// Trace
TraceCall("CDatabase::_CompactMoveOpenDeletedStreams");
// Loop through the stream table and adjust the start address of all open streams
for (i=0; i<CMAX_OPEN_STREAMS; i++)
{
// Readability
pInfo = &m_pShare->rgStream[i];
// Is In use...
if (FALSE == pInfo->fInUse || FALSE == pInfo->fDeleteOnClose)
continue;
// Move the Stream
IF_FAILEXIT(hr = CopyStream((IDatabase *)pDstDB, pInfo->faStart, &pInfo->faMoved));
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CompactTransferFilters
//--------------------------------------------------------------------------
HRESULT CDatabase::_CompactTransferFilters(CDatabase *pDstDB)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
LPBLOCKHEADER pStringSrc;
LPBLOCKHEADER pStringDst;
// Trace
TraceCall("CDatabase::_CompactTransferFilters");
// Must have a Catalog
Assert(pDstDB->m_pHeader);
// Zero Out the Query String Addresses
for (i=0; i<CMAX_INDEXES; i++)
{
// Zero Filter1
pDstDB->m_pHeader->rgfaFilter[i] = 0;
// Copy Filter1
if (m_pHeader->rgfaFilter[i] && SUCCEEDED(_GetBlock(BLOCK_STRING, m_pHeader->rgfaFilter[i], (LPVOID *)&pStringSrc)))
{
// Try to Store the Query String
IF_FAILEXIT(hr = pDstDB->_AllocateBlock(BLOCK_STRING, pStringSrc->cbSize, (LPVOID *)&pStringDst));
// Write the String
CopyMemory(PSTRING(pStringDst), PSTRING(pStringSrc), pStringSrc->cbSize);
// String the String Address
pDstDB->m_pHeader->rgfaFilter[i] = pStringDst->faBlock;
}
}
// Change the Version so that it doesn't assert
pDstDB->m_dwQueryVersion = 0xffffffff;
// Rebuild the Query Table
IF_FAILEXIT(hr = pDstDB->_BuildQueryTable());
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CompactInsertRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_CompactInsertRecord(LPVOID pBinding)
{
// Locals
HRESULT hr=S_OK;
FINDRESULT rgResult[CMAX_INDEXES];
INDEXORDINAL iIndex;
DWORD i;
RECORDMAP RecordMap;
FILEADDRESS faRecord;
// Trace
TraceCall("CDatabase::InsertRecord");
// Invalid Args
Assert(pBinding);
// Loop through all the indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Otherwise: Decide Where to insert
IF_FAILEXIT(hr = _FindRecord(iIndex, COLUMNS_ALL, pBinding, &rgResult[i].faChain, &rgResult[i].iNode, NULL, &rgResult[i].nCompare));
// If key already exist, cache list and return
if (DB_S_FOUND == hr)
{
hr = TraceResult(DB_E_DUPLICATE);
goto exit;
}
}
// Get the Record Size
IF_FAILEXIT(hr = _GetRecordSize(pBinding, &RecordMap));
// Link Record Into the Table
IF_FAILEXIT(hr = _LinkRecordIntoTable(&RecordMap, pBinding, 0, &faRecord));
// Version Change
m_pShare->dwVersion++;
// Insert into the indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Visible in live index
if (S_OK == _IsVisible(m_rghFilter[iIndex], pBinding))
{
// Do the Insertion
IF_FAILEXIT(hr = _IndexInsertRecord(iIndex, rgResult[i].faChain, faRecord, &rgResult[i].iNode, rgResult[i].nCompare));
// Update Record Count
m_pHeader->rgcRecords[iIndex]++;
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::MoveFile
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::MoveFile(LPCWSTR pszFile)
{
// Locals
HRESULT hr=S_OK;
HLOCK hLock=NULL;
LPWSTR pszFilePath=NULL;
LPWSTR pszShare=NULL;
DWORD cchFilePath;
HANDLE hMutex=NULL;
BOOL fNeedOpenFile=FALSE;
BOOL fNewShare;
SHAREDDATABASE Share;
// Trace
TraceCall("CDatabase::MoveFile");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// In move File
m_fInMoveFile = TRUE;
// Get the Full Path
IF_FAILEXIT(hr = DBGetFullPath(pszFile, &pszFilePath, &cchFilePath));
// Don't use pszFile again
pszFile = NULL;
// Failure
if (cchFilePath >= CCHMAX_DB_FILEPATH)
{
hr = TraceResult(E_INVALIDARG);
goto exit;
}
// Do It
IF_FAILEXIT(hr = _DispatchInvoke(INVOKE_CLOSEFILE));
// Need a remap..
fNeedOpenFile = TRUE;
// Move the file from the temp location to my current location
if (0 == MoveFileWrapW(m_pShare->szFile, pszFilePath))
{
hr = TraceResult(DB_E_MOVEFILE);
goto exit;
}
// Save the new file path...(other clients will remap to this file...)
StrCpyNW(m_pShare->szFile, pszFilePath, ARRAYSIZE(m_pShare->szFile));
// Save the Current Share
CopyMemory(&Share, m_pShare, sizeof(SHAREDDATABASE));
// Save Current Mutex
hMutex = m_hMutex;
// Clear m_hMutex so that we don't free it
m_hMutex = NULL;
// Create the Mutex Object
IF_FAILEXIT(hr = CreateSystemHandleName(pszFilePath, L"_DirectDBShare", &pszShare));
// Unmap the view of the memory mapped file
SafeUnmapViewOfFile(m_pShare);
// Unmap the view of the memory mapped file
SafeCloseHandle(m_pStorage->hShare);
// Open the file mapping
IF_FAILEXIT(hr = DBOpenFileMapping(INVALID_HANDLE_VALUE, pszShare, sizeof(SHAREDDATABASE), &fNewShare, &m_pStorage->hShare, (LPVOID *)&m_pShare));
// Should be new
Assert(fNewShare);
// Save the Current Share
CopyMemory(m_pShare, &Share, sizeof(SHAREDDATABASE));
// Get all clients to re-open the new file
IF_FAILEXIT(hr = _DispatchInvoke(INVOKE_OPENMOVEDFILE));
// Validate Mutex Changed
Assert(m_hMutex && hMutex != m_hMutex);
// Enter New Mutex
WaitForSingleObject(m_hMutex, INFINITE);
// Fix hLock
hLock = (HLOCK)m_hMutex;
// Release hMutex
ReleaseMutex(hMutex);
// Free hMutex
SafeCloseHandle(hMutex);
// Sucess
fNeedOpenFile = FALSE;
exit:
// Not In Move File
m_fInMoveFile = FALSE;
// If Need Open File
if (fNeedOpenFile)
{
// Try to re-open the file..
_DispatchInvoke(INVOKE_OPENFILE);
}
// Unlock
Unlock(&hLock);
// Cleanup
SafeMemFree(pszFilePath);
SafeMemFree(pszShare);
// done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::Compact
//--------------------------------------------------------------------------
STDMETHODIMP CDatabase::Compact(IDatabaseProgress *pProgress, COMPACTFLAGS dwFlags)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
DWORD dwVersion;
DWORDLONG dwlFree;
DWORD cDuplicates=0;
DWORD cRecords=0;
DWORD cbDecrease;
LPVOID pBinding=NULL;
DWORD dwNextId;
DWORD cbWasted;
HLOCK hLock=NULL;
HLOCK hDstLock=NULL;
DWORD cActiveThreads;
LPWSTR pszDstFile=NULL;
HROWSET hRowset=NULL;
DWORD cch;
CDatabase *pDstDB=NULL;
// Trace
TraceCall("CDatabase::Compact");
// Lock
IF_FAILEXIT(hr = Lock(&hLock));
// If Compacting...
if (TRUE == m_pShare->fCompacting)
{
// Leave Spin Lock
Unlock(&hLock);
// Trace
return(TraceResult(DB_E_COMPACTING));
}
// I am compacting
m_pShare->fCompacting = TRUE;
// Yield
if (ISFLAGSET(dwFlags, COMPACT_YIELD))
{
// Yield ?
m_fCompactYield = TRUE;
}
// Get Length
cch = lstrlenW(m_pShare->szFile);
//Bug #101511: (erici) Debug shlwapi validates it to MAX_PATH characters
if( (cch+15) < MAX_PATH)
{
cch = MAX_PATH-15;
}
// Create .dbt file
IF_NULLEXIT(pszDstFile = AllocateStringW(cch + 15));
// Copy File Name
StrCpyNW(pszDstFile, m_pShare->szFile, (cch+15));
// Change the Extension
PathRenameExtensionW(pszDstFile, L".dbt");
// Delete that file
DeleteFileWrapW(pszDstFile);
// Delete my Current File
if (PathFileExistsW(pszDstFile))
{
hr = TraceResult(E_FAIL);
goto exit;
}
// Loop through the stream table and see if there are any streams open for a write
for (i=0; i<CMAX_OPEN_STREAMS; i++)
{
// Is In use...
if (TRUE == m_pShare->rgStream[i].fInUse && LOCK_VALUE_WRITER == m_pShare->rgStream[i].lLock)
{
hr = TraceResult(DB_E_COMPACT_PREEMPTED);
goto exit;
}
}
// If there are pending notifications...
if (m_pHeader->cTransacts > 0)
{
hr = TraceResult(DB_E_DATABASE_CHANGED);
goto exit;
}
// Is there enought disk space where pDstDB is located
IF_FAILEXIT(hr = GetAvailableDiskSpace(m_pShare->szFile, &dwlFree));
// Compute cbWasted
cbWasted = (m_pHeader->cbFreed + (m_pStorage->cbFile - m_pHeader->faNextAllocate));
// Is there enough disk space ?
if (dwlFree <= ((DWORDLONG) (m_pStorage->cbFile - cbWasted)))
{
hr = TraceResult(DB_E_DISKFULL);
goto exit;
}
// Create the Object Database Object
IF_NULLEXIT(pDstDB = new CDatabase);
// Open the Table
IF_FAILEXIT(hr = pDstDB->Open(pszDstFile, OPEN_DATABASE_NOEXTENSION | OPEN_DATABASE_NOMONITOR, m_pSchema, NULL));
// Lock the Destination Database
IF_FAILEXIT(hr = pDstDB->Lock(&hDstLock));
// Get user info from current tree
if (m_pSchema->cbUserData)
{
// Set the user data
IF_FAILEXIT(hr = pDstDB->SetUserData(PUSERDATA(m_pHeader), m_pSchema->cbUserData));
}
// I'm going to grow the destination to be as big as the current file (I will truncate when finished)
IF_FAILEXIT(hr = pDstDB->SetSize(m_pStorage->cbFile - cbWasted));
// Set number of indexes
pDstDB->m_pHeader->cIndexes = m_pHeader->cIndexes;
// Copy Index Information...
CopyMemory((LPBYTE)pDstDB->m_pHeader->rgIndexInfo, (LPBYTE)m_pHeader->rgIndexInfo, sizeof(TABLEINDEX) * CMAX_INDEXES);
// Copy Index Information...
CopyMemory((LPBYTE)pDstDB->m_pHeader->rgiIndex, (LPBYTE)m_pHeader->rgiIndex, sizeof(INDEXORDINAL) * CMAX_INDEXES);
// Transfer Query Table...
IF_FAILEXIT(hr = _CompactTransferFilters(pDstDB));
// Allocate a Record
IF_NULLEXIT(pBinding = PHeapAllocate(HEAP_ZERO_MEMORY, m_pSchema->cbBinding));
// Create a Rowset
IF_FAILEXIT(hr = CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowset));
// Save new Version
dwVersion = m_pShare->dwVersion;
// While we have a node address
while (S_OK == QueryRowset(hRowset, 1, (LPVOID *)pBinding, NULL))
{
// Can Preempt
if (ISFLAGSET(dwFlags, COMPACT_PREEMPTABLE) && m_pShare->cWaitingForLock > 0)
{
hr = TraceResult(DB_E_COMPACT_PREEMPTED);
goto exit;
}
// If the record has streams
if (ISFLAGSET(m_pSchema->dwFlags, TSF_HASSTREAMS))
{
// Compact Move Record Streams
IF_FAILEXIT(hr = _CompactMoveRecordStreams(pDstDB, pBinding));
}
// Insert Record Into Destination
hr = pDstDB->_CompactInsertRecord(pBinding);
// Duplicate
if (DB_E_DUPLICATE == hr)
{
// Trace
TraceResult(DB_E_DUPLICATE);
// Reset Hr
hr = S_OK;
// Count
cDuplicates++;
}
// Failed ?
else if (FAILED (hr))
{
TraceResult(hr);
goto exit;
}
// Count
cRecords++;
// Free this Record
FreeRecord(pBinding);
// Update the Progress...
if (pProgress)
{
// Call into the progress object
IF_FAILEXIT(hr = pProgress->Update(1));
// Version Change ?
if (dwVersion != m_pShare->dwVersion || m_pHeader->cTransacts > 0)
{
hr = TraceResult(DB_E_DATABASE_CHANGED);
goto exit;
}
}
// Yield
if (ISFLAGSET(dwFlags, COMPACT_YIELD))
{
// this will force this thread to give up a time-slice
Sleep(0);
}
}
// Duplicates ?
AssertSz(cDuplicates == 0, "Duplicates were found in the tree. They have been eliminated.");
// Copy over deleted streams that are currently open...
IF_FAILEXIT(hr = _CompactMoveOpenDeletedStreams(pDstDB));
// Number of records better be equal
AssertSz(cRecords == m_pHeader->rgcRecords[0], "Un-expected number of records compacted");
// Save dwNextId
dwNextId = m_pHeader->dwNextId;
// Save Active Threads
cActiveThreads = m_pHeader->cActiveThreads;
// Compute amount to decrease the file by
cbDecrease = (pDstDB->m_pStorage->cbFile - pDstDB->m_pHeader->faNextAllocate);
// Reduce the Size of myself...
IF_FAILEXIT(hr = pDstDB->_SetStorageSize(pDstDB->m_pStorage->cbFile - cbDecrease));
// Unlock the file
pDstDB->Unlock(&hDstLock);
// Release pDstDB
SafeRelease(pDstDB);
// Do It
IF_FAILEXIT(hr = _DispatchInvoke(INVOKE_CLOSEFILE));
// Delete my Current File
if (0 == DeleteFileWrapW(m_pShare->szFile))
{
// Failure
hr = TraceResult(E_FAIL);
// Try to re-open the file..
_DispatchInvoke(INVOKE_OPENFILE);
// Done
goto exit;
}
// Move the file from the temp location to my current location
if (0 == MoveFileWrapW(pszDstFile, m_pShare->szFile))
{
// Trace
hr = TraceResult(DB_E_MOVEFILE);
// Try to re-open the file..
_DispatchInvoke(INVOKE_OPENFILE);
// Done
goto exit;
}
// Do It
IF_FAILEXIT(hr = _DispatchInvoke(INVOKE_OPENFILE));
// Reset Active Thread Count
m_pHeader->cActiveThreads = cActiveThreads;
// Reset dwNextId
m_pHeader->dwNextId = dwNextId;
// Reset Notification Queue
Assert(0 == m_pHeader->faTransactHead && 0 == m_pHeader->faTransactTail);
// Reset Transaction List
m_pHeader->faTransactHead = m_pHeader->faTransactTail = m_pHeader->cTransacts = 0;
// Reset Share Transacts
m_pShare->faTransactLockHead = m_pShare->faTransactLockTail = 0;
// Loop through the stream table and adjust the start address of all open streams
for (i=0; i<CMAX_OPEN_STREAMS; i++)
{
// Is In use...
if (TRUE == m_pShare->rgStream[i].fInUse)
{
// Change the Address
m_pShare->rgStream[i].faStart = m_pShare->rgStream[i].faMoved;
}
}
// Build the Update Notification Package
_LogTransaction(TRANSACTION_COMPACTED, INVALID_INDEX_ORDINAL, NULL, 0, 0);
exit:
// Close my rowset
CloseRowset(&hRowset);
// Release Dst Lock
if (pDstDB && hDstLock)
pDstDB->Unlock(&hDstLock);
// Release the memory mapped pointers
SafeRelease(pDstDB);
// Free the Record
SafeFreeBinding(pBinding);
// No Longer compacting
m_pShare->fCompacting = FALSE;
// Delete pszDstFile
if (pszDstFile)
{
// Delete the file
DeleteFileWrapW(pszDstFile);
// Remaining Cleanup
SafeMemFree(pszDstFile);
}
// Reset Yield
m_fCompactYield = FALSE;
// Release Locks
Unlock(&hLock);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_CheckForCorruption
//--------------------------------------------------------------------------
HRESULT CDatabase::_CheckForCorruption(void)
{
// Locals
HRESULT hr=S_OK;
ULONG cRecords;
DWORD i;
HRESULT rghrCorrupt[CMAX_INDEXES]={0};
DWORD cCorrupt=0;
INDEXORDINAL iIndex;
// Trace
TraceCall("CDatabase::_CheckForCorruption");
// We should not be currently repairing
Assert(FALSE == m_pShare->fRepairing);
// We are now repairing
IF_DEBUG(m_pShare->fRepairing = TRUE);
// Walk Through the Indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Zero Out cRecords
cRecords = 0;
// Assume all is good
rghrCorrupt[iIndex] = S_OK;
// Start at the root
if (m_pHeader->rgfaIndex[iIndex])
{
// Validate the Index
rghrCorrupt[iIndex] = _ValidateIndex(iIndex, m_pHeader->rgfaIndex[iIndex], 0, &cRecords);
}
// If Not Corrupt, validate the record counts
if (DB_E_CORRUPT != rghrCorrupt[iIndex] && m_pHeader->rgcRecords[iIndex] != cRecords)
{
// Its Corrupt
rghrCorrupt[iIndex] = TraceResult(DB_E_CORRUPT);
}
// If Corrupt
if (DB_E_CORRUPT == rghrCorrupt[iIndex])
{
// Count Number of Corrupted records
cCorrupt += m_pHeader->rgcRecords[iIndex];
}
}
// Are the Corrupt Records
if (cCorrupt > 0 || m_pHeader->fCorrupt)
{
// I'm going to nuke the free block tables since they may also be corrupted...
ZeroMemory(m_pHeader->rgfaFreeBlock, sizeof(FILEADDRESS) * CC_FREE_BUCKETS);
// Reset Fixed blocks
m_pHeader->faFreeStreamBlock = m_pHeader->faFreeChainBlock = m_pHeader->faFreeLargeBlock = 0;
// Nuke the Transaction List
m_pHeader->cTransacts = m_pHeader->faTransactHead = m_pHeader->faTransactTail = 0;
// Nuke The Fixed Block Allocation Pages
ZeroMemory(&m_pHeader->AllocateRecord, sizeof(ALLOCATEPAGE));
ZeroMemory(&m_pHeader->AllocateChain, sizeof(ALLOCATEPAGE));
ZeroMemory(&m_pHeader->AllocateStream, sizeof(ALLOCATEPAGE));
// Reset rgcbAllocated
ZeroMemory(m_pHeader->rgcbAllocated, sizeof(DWORD) * CC_MAX_BLOCK_TYPES);
// Reset faNextAllocate
m_pHeader->faNextAllocate = m_pStorage->cbFile;
// Walk Through the Indexes
for (i = 0; i < m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// If Corrupt
if (DB_E_CORRUPT == rghrCorrupt[iIndex])
{
// Not Corrupt
m_pHeader->fCorrupt = TRUE;
// Rebuild the Index
IF_FAILEXIT(hr = _RebuildIndex(iIndex));
}
}
}
// Not Corrupt
m_pHeader->fCorrupt = FALSE;
// This causes all current file views to be flushed and released. Insures we are in a good state.
#ifdef BACKGROUND_MONITOR
DoBackgroundMonitor();
#else
CloseFileViews(TRUE);
#endif
exit:
// We are now repairing
IF_DEBUG(m_pShare->fRepairing = FALSE);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_FreeIndex
//--------------------------------------------------------------------------
HRESULT CDatabase::_FreeIndex(FILEADDRESS faChain)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
LPCHAINBLOCK pChain;
// Trace
TraceCall("CDatabase::_FreeIndex");
// Nothing to validate
if (0 == faChain)
return(S_OK);
// Validate faChain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Go to the left
IF_FAILEXIT(hr = _FreeIndex(pChain->faLeftChain));
// Loop throug right chains
for (i=0; i<pChain->cNodes; i++)
{
// Validate the Right Chain
IF_FAILEXIT(hr = _FreeIndex(pChain->rgNode[i].faRightChain));
}
// Free this Chain
IF_FAILEXIT(hr = _FreeBlock(BLOCK_CHAIN, faChain));
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_ValidateIndex
//--------------------------------------------------------------------------
HRESULT CDatabase::_ValidateIndex(INDEXORDINAL iIndex,
FILEADDRESS faChain, ULONG cLeftNodes, ULONG *pcRecords)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
LPCHAINBLOCK pChain;
LPRECORDBLOCK pRecord;
DWORD cLeafs=0;
DWORD cNodes;
RECORDMAP Map;
// Trace
TraceCall("CDatabase::_ValidateIndex");
// Nothing to validate
Assert(0 != faChain);
// Get Chain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Validate Minimum Filled Constraint
if (pChain->cNodes < BTREE_MIN_CAP && pChain->faBlock != m_pHeader->rgfaIndex[iIndex])
return TraceResult(DB_E_CORRUPT);
// Validate faParent
if (pChain->faParent)
{
// Locals
LPCHAINBLOCK pParent;
// Get Parent
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, pChain->faParent, (LPVOID *)&pParent));
// Validate iParent
if (pChain->iParent >= pParent->cNodes)
return TraceResult(DB_E_CORRUPT);
// Validate the Parent Pointer
if (0 == pChain->iParent)
{
// Validation
if (pParent->rgNode[pChain->iParent].faRightChain != pChain->faBlock && pParent->faLeftChain != pChain->faBlock)
return TraceResult(DB_E_CORRUPT);
}
// Otherwise
else if (pParent->rgNode[pChain->iParent].faRightChain != pChain->faBlock)
return TraceResult(DB_E_CORRUPT);
}
// Otherwise, iParent should be zero...
else
{
// This is the root chain
if (m_pHeader->rgfaIndex[iIndex] != pChain->faBlock)
return TraceResult(DB_E_CORRUPT);
// iParent should be 0
Assert(pChain->iParent == 0);
}
// Do the Left
if (pChain->faLeftChain)
{
// Go to the left
IF_FAILEXIT(hr = _ValidateIndex(iIndex, pChain->faLeftChain, cLeftNodes, pcRecords));
}
// cNodes
cNodes = pChain->cLeftNodes;
// Validate the Records in this chain
for (i=0; i<pChain->cNodes; i++)
{
// Count the number of leaf nodes
cLeafs += (0 == pChain->rgNode[i].faRightChain) ? 1 : 0;
// cNodes
cNodes += pChain->rgNode[i].cRightNodes;
}
// Validate the Number of Leafs
if (cLeafs > 0 && cLeafs != (DWORD)pChain->cNodes)
return TraceResult(DB_E_CORRUPT);
// No leafs, but their are child nodes, or vice vera...
if ((0 != cLeafs && 0 != cNodes) || (0 == cLeafs && 0 == cNodes))
return TraceResult(DB_E_CORRUPT);
// Loop throug right chains
for (i=0; i<pChain->cNodes; i++)
{
// Try to get the record, if the block is invalid, we will throw away the record
if (SUCCEEDED(_GetBlock(BLOCK_RECORD, pChain->rgNode[i].faRecord, (LPVOID *)&pRecord, FALSE)))
{
// Validate Block...
if (SUCCEEDED(_GetRecordMap(FALSE, pRecord, &Map)))
{
// Validate and Repair the Record
if (S_OK == _ValidateAndRepairRecord(&Map))
{
// Count Records
(*pcRecords)++;
}
}
}
// First Node ?
if (0 == i)
{
// Increment cLeft Nodes
cLeftNodes += pChain->cLeftNodes;
}
// Otherwise
else
{
// Increment cLeftNodes
cLeftNodes += pChain->rgNode[i - 1].cRightNodes;
}
// Failure
if ((*pcRecords) != cLeftNodes + 1)
return TraceResult(DB_E_CORRUPT);
// Increment cLeftNodes
cLeftNodes++;
// Do the Right
if (pChain->rgNode[i].faRightChain)
{
// Validate the Right Chain
IF_FAILEXIT(hr = _ValidateIndex(iIndex, pChain->rgNode[i].faRightChain, cLeftNodes, pcRecords));
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_RebuildIndex
//--------------------------------------------------------------------------
HRESULT CDatabase::_RebuildIndex(INDEXORDINAL iIndex)
{
// Locals
HRESULT hr=S_OK;
LPVOID pBinding=NULL;
DWORD cRecords=0;
FILEADDRESS faPrimary;
// Trace
TraceCall("CDatabase::_RebuildIndex");
// Allocate a record
IF_NULLEXIT(pBinding = PHeapAllocate(HEAP_ZERO_MEMORY, m_pSchema->cbBinding));
// Save Primary Index Starting Address
faPrimary = m_pHeader->rgfaIndex[IINDEX_PRIMARY];
// Reset rgfaIndex[iIndex]
m_pHeader->rgfaIndex[iIndex] = 0;
// Is there a root chain ?
if (faPrimary)
{
// Recursively Rebuild this index
IF_FAILEXIT(hr = _RecursiveRebuildIndex(iIndex, faPrimary, pBinding, &cRecords));
}
// Fixup Record Count
m_pHeader->rgcRecords[iIndex] = cRecords;
// Send Notifications ?
if (m_pShare->rgcIndexNotify[iIndex] > 0)
{
// Build the Update Notification Package
_LogTransaction(TRANSACTION_INDEX_CHANGED, iIndex, NULL, 0, 0);
}
exit:
// Cleanup
SafeFreeBinding(pBinding);
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_RecursiveRebuildIndex
//--------------------------------------------------------------------------
HRESULT CDatabase::_RecursiveRebuildIndex(INDEXORDINAL iIndex,
FILEADDRESS faCurrent, LPVOID pBinding, LPDWORD pcRecords)
{
// Locals
NODEINDEX i;
FILEADDRESS faRecord;
FILEADDRESS faChain;
NODEINDEX iNode;
CHAINBLOCK Chain;
LPCHAINBLOCK pChain;
INT nCompare;
BOOL fGoodRecord=TRUE;
RECORDMAP Map;
LPRECORDBLOCK pRecord;
// Trace
TraceCall("CDatabase::_RecursiveRebuildIndex");
// Nothing to validate
Assert(0 != faCurrent);
// Validate faChain
if (FAILED(_GetBlock(BLOCK_CHAIN, faCurrent, (LPVOID *)&pChain)))
return(S_OK);
// Copy This
CopyMemory(&Chain, pChain, sizeof(CHAINBLOCK));
// Do the left
if (Chain.faLeftChain)
{
// Go to the left
_RecursiveRebuildIndex(iIndex, Chain.faLeftChain, pBinding, pcRecords);
}
// Loop throug right chains
for (i=0; i<Chain.cNodes; i++)
{
// Set faRecord
faRecord = Chain.rgNode[i].faRecord;
// Get the Block
if (SUCCEEDED(_GetBlock(BLOCK_RECORD, faRecord, (LPVOID *)&pRecord, NULL, FALSE)))
{
// Rebuilding Primary Index ?
if (IINDEX_PRIMARY == iIndex)
{
// Assume this is a bad record
fGoodRecord = FALSE;
// Try to get the record map
if (SUCCEEDED(_GetRecordMap(FALSE, pRecord, &Map)))
{
// Validate Map ?
if (S_OK == _ValidateAndRepairRecord(&Map))
{
// Good Record
fGoodRecord = TRUE;
}
}
}
// Good Record ?
if (fGoodRecord)
{
// Load the Record
if (SUCCEEDED(_ReadRecord(faRecord, pBinding, TRUE)))
{
// Reset hrVisible
if (S_OK == _IsVisible(m_rghFilter[iIndex], pBinding))
{
// Otherwise: Decide Where to insert
if (DB_S_NOTFOUND == _FindRecord(iIndex, COLUMNS_ALL, pBinding, &faChain, &iNode, NULL, &nCompare))
{
// Insert the Record
if (SUCCEEDED(_IndexInsertRecord(iIndex, faChain, faRecord, &iNode, nCompare)))
{
// Increment Record Count
(*pcRecords)++;
}
}
}
}
}
}
// Do the Right
if (Chain.rgNode[i].faRightChain)
{
// Index the Right Chain
_RecursiveRebuildIndex(iIndex, Chain.rgNode[i].faRightChain, pBinding, pcRecords);
}
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_ValidateStream
//--------------------------------------------------------------------------
HRESULT CDatabase::_ValidateStream(FILEADDRESS faStart)
{
// Locals
LPSTREAMBLOCK pStream;
FILEADDRESS faCurrent;
// Trace
TraceCall("CDatabase::_ValidateStream");
// No Stream
if (0 == faStart)
return(S_OK);
// Initialize Loop
faCurrent = faStart;
// Read through all of the blocks (i.e. verify headers and count the number of chains)
while (faCurrent)
{
// Valid stream Block
if (FAILED(_GetBlock(BLOCK_STREAM, faCurrent, (LPVOID *)&pStream)))
return(S_FALSE);
// Validate cbData
if (pStream->cbData > pStream->cbSize)
return(S_FALSE);
// Set faCurrent
faCurrent = pStream->faNext;
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_ValidateAndRepairRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_ValidateAndRepairRecord(LPRECORDMAP pMap)
{
// Locals
LPCTABLECOLUMN pColumn;
LPCOLUMNTAG pTag;
WORD iTag;
// Trace
TraceCall("CDatabase::_ValidateAndRepairRecord");
// Walk through the Tags of the Record
for (iTag=0; iTag<pMap->cTags; iTag++)
{
// Readability
pTag = &pMap->prgTag[iTag];
// Validate the Tag
if (pTag->iColumn >= m_pSchema->cColumns)
return(S_FALSE);
// De-ref the Column
pColumn = &m_pSchema->prgColumn[pTag->iColumn];
// Read the Data
if (S_FALSE == DBTypeValidate(pColumn, pTag, pMap))
return(S_FALSE);
// Is this a stream ?
if (CDT_STREAM == pColumn->type)
{
// Locals
FILEADDRESS faStream;
// Get the faStream
if (1 == pTag->fData)
faStream = pTag->Offset;
else
faStream = *((DWORD *)(pMap->pbData + pTag->Offset));
// Validate this stream...
if (S_FALSE == _ValidateStream(faStream))
{
// Kill the stream address...
if (1 == pTag->fData)
pTag->Offset = 0;
else
*((DWORD *)(pMap->pbData + pTag->Offset)) = 0;
}
}
// Unique Key ?
if (CDT_UNIQUE == pColumn->type)
{
// Locals
DWORD dwUniqueID;
// Get the dwUniqueID
if (1 == pTag->fData)
dwUniqueID = pTag->Offset;
else
dwUniqueID = *((DWORD *)(pMap->pbData + pTag->Offset));
// Adjust the id in the header ?
if (dwUniqueID >= m_pHeader->dwNextId)
m_pHeader->dwNextId = dwUniqueID + 1;
}
}
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// CDatabase::_GetRecordMap
//--------------------------------------------------------------------------
HRESULT CDatabase::_GetRecordMap(BOOL fGoCorrupt, LPRECORDBLOCK pBlock,
LPRECORDMAP pMap)
{
// Trace
TraceCall("CDatabase::_GetRecordMap");
// Invalid Args
Assert(pBlock && pMap);
// Set pSchema
pMap->pSchema = m_pSchema;
// Store Number of Tags
pMap->cTags = min(pBlock->cTags, m_pSchema->cColumns);
// Set prgTag
pMap->prgTag = (LPCOLUMNTAG)((LPBYTE)pBlock + sizeof(RECORDBLOCK));
// Compute Size of Tags
pMap->cbTags = (pBlock->cTags * sizeof(COLUMNTAG));
// Compute Size of Data
pMap->cbData = (pBlock->cbSize - pMap->cbTags);
// Set pbData
pMap->pbData = (LPBYTE)((LPBYTE)pBlock + sizeof(RECORDBLOCK) + pMap->cbTags);
// No Tags - this is usually the sign of a freeblock that was reused but not allocated
if (0 == pMap->cTags)
return _SetCorrupt(fGoCorrupt, __LINE__, REASON_INVALIDRECORDMAP, BLOCK_RECORD, pBlock->faBlock, pBlock->faBlock, pBlock->cbSize);
// Too many tags
if (pMap->cTags > m_pSchema->cColumns)
return _SetCorrupt(fGoCorrupt, __LINE__, REASON_INVALIDRECORDMAP, BLOCK_RECORD, pBlock->faBlock, pBlock->faBlock, pBlock->cbSize);
// cbTags is too large ?
if (pMap->cbTags > pBlock->cbSize)
return _SetCorrupt(fGoCorrupt, __LINE__, REASON_INVALIDRECORDMAP, BLOCK_RECORD, pBlock->faBlock, pBlock->faBlock, pBlock->cbSize);
// Done
return(S_OK);
}
#ifdef DEBUG
//--------------------------------------------------------------------------
// DBDebugValidateRecordFormat
//--------------------------------------------------------------------------
HRESULT CDatabase::_DebugValidateRecordFormat(void)
{
// Locals
ULONG i;
DWORD dwOrdinalPrev=0;
DWORD dwOrdinalMin=0xffffffff;
DWORD dwOrdinalMax=0;
// Validate memory buffer binding offset
Assert(0xFFFFFFFF != m_pSchema->ofMemory && m_pSchema->ofMemory < m_pSchema->cbBinding);
// Validate version binding offset
Assert(0xFFFFFFFF != m_pSchema->ofVersion && m_pSchema->ofVersion < m_pSchema->cbBinding);
// Validate Extension
Assert(*m_pSchema->pclsidExtension != CLSID_NULL);
// Validate Version
Assert(m_pSchema->dwMinorVersion != 0);
// Check Number of Indexes
Assert(m_pSchema->pPrimaryIndex);
// Loop through they Keys
for (i=0; i<m_pSchema->cColumns; i++)
{
// This Ordinal better be larger than the previous
if (i > 0)
Assert(m_pSchema->prgColumn[i].iOrdinal > dwOrdinalPrev);
// Save Min Ordinal
if (m_pSchema->prgColumn[i].iOrdinal < dwOrdinalMin)
dwOrdinalMin = m_pSchema->prgColumn[i].iOrdinal;
// Save Max Ordinal
if (m_pSchema->prgColumn[i].iOrdinal > dwOrdinalMax)
dwOrdinalMax = m_pSchema->prgColumn[i].iOrdinal;
// Save the Previous Ordinal
dwOrdinalPrev = m_pSchema->prgColumn[i].iOrdinal;
}
// Min ordinal must be one
Assert(dwOrdinalMin == 0);
// Done
return(S_OK);
}
//--------------------------------------------------------------------------
// _DebugValidateUnrefedRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_DebugValidateUnrefedRecord(FILEADDRESS faRecord)
{
// Locals
HRESULT hr=S_OK;
DWORD i;
INDEXORDINAL iIndex;
// Trace
TraceCall("CDatabase::_DebugValidateUnrefedRecord");
// Walk Through the Indexes
for (i=0; i<m_pHeader->cIndexes; i++)
{
// Get Index Ordinal
iIndex = m_pHeader->rgiIndex[i];
// Start at the root
if (m_pHeader->rgfaIndex[iIndex])
{
// Validate the Index
IF_FAILEXIT(hr = _DebugValidateIndexUnrefedRecord(m_pHeader->rgfaIndex[iIndex], faRecord));
}
}
exit:
// Done
return(hr);
}
//--------------------------------------------------------------------------
// CDatabase::_DebugValidateIndexUnrefedRecord
//--------------------------------------------------------------------------
HRESULT CDatabase::_DebugValidateIndexUnrefedRecord(FILEADDRESS faChain,
FILEADDRESS faRecord)
{
// Locals
HRESULT hr=S_OK;
NODEINDEX i;
LPCHAINBLOCK pChain;
// Trace
TraceCall("CDatabase::_DebugValidateIndexUnrefedRecord");
// Nothing to validate
Assert(0 != faChain);
// Validate faChain
IF_FAILEXIT(hr = _GetBlock(BLOCK_CHAIN, faChain, (LPVOID *)&pChain));
// Do the left
if (pChain->faLeftChain)
{
// Go to the left
IF_FAILEXIT(hr = _DebugValidateIndexUnrefedRecord(pChain->faLeftChain, faRecord));
}
// Loop throug right chains
for (i=0; i<pChain->cNodes; i++)
{
// Set faRecord
if (faRecord == pChain->rgNode[i].faRecord)
{
IxpAssert(FALSE);
hr = TraceResult(E_FAIL);
goto exit;
}
// Do the Right
if (pChain->rgNode[i].faRightChain)
{
// Index the Right Chain
IF_FAILEXIT(hr = _DebugValidateIndexUnrefedRecord(pChain->rgNode[i].faRightChain, faRecord));
}
}
exit:
// Done
return(hr);
}
#endif // DEBUG