//*************************************************************************** // // BTR.CPP // // WMI disk-based B-tree implementation for repository index // // raymcc 15-Oct-00 Prepared for Whistler Beta 2 to reduce file count // //*************************************************************************** #include #include #include #include #include #include #include #include #define MAX_WORD_VALUE 0xFFFF #define MAX_TOKENS_PER_KEY 32 #define MAX_FLUSH_INTERVAL 4000 //#define MAX_PAGE_HISTORY 1024 /* Notes: (a) Modify allocators to special case for page-size (b) Modify WriteIdxPage to not rewrite if no deltas (c) ERROR_PATH_NOT_FOUND if starting enum has no presence; GPF presently (d) Do a history of page hits and see if caching would be helpful */ //static WORD History[MAX_PAGE_HISTORY] = {0}; LONG g_lAllocs = 0; //*************************************************************************** // // _BtrMemAlloc // //*************************************************************************** // ok LPVOID WINAPI _BtrMemAlloc( SIZE_T dwBytes // number of bytes to allocate ) { // Lookaside for items of page size, default array size, default // string pool size g_lAllocs++; return HeapAlloc(CWin32DefaultArena::GetArenaHeap(), HEAP_ZERO_MEMORY, dwBytes); } //*************************************************************************** // // _BtrMemReAlloc // //*************************************************************************** // ok LPVOID WINAPI _BtrMemReAlloc( LPVOID pOriginal, DWORD dwNewBytes ) { return HeapReAlloc(CWin32DefaultArena::GetArenaHeap(), HEAP_ZERO_MEMORY, pOriginal, dwNewBytes); } //*************************************************************************** // // _BtrMemFree // //*************************************************************************** // ok BOOL WINAPI _BtrMemFree(LPVOID pMem) { if (pMem == 0) return TRUE; g_lAllocs--; return HeapFree(CWin32DefaultArena::GetArenaHeap(), 0, pMem); } //*************************************************************************** // // CPageSource::CPageSource // //*************************************************************************** // ok CPageSource::CPageSource() { m_dwPageSize = 0; m_hFile = INVALID_HANDLE_VALUE; m_dwTotalPages = 0; m_dwLogicalRoot = 0; m_dwNextFreePage = 0; m_dwLastFlushTime = GetCurrentTime(); } //*************************************************************************** // // CPageSource::CPageSource // //*************************************************************************** // ok CPageSource::~CPageSource() { } //*************************************************************************** // // CPageSource::Shutdown // //*************************************************************************** // ok DWORD CPageSource::Shutdown(DWORD dwShutDownFlags) { #ifdef A51_STAGE_BELOW_INDEX #else CloseHandle(m_hFile); #endif m_dwPageSize = 0; m_hFile = INVALID_HANDLE_VALUE; m_dwTotalPages = 0; m_dwLogicalRoot = 0; m_dwNextFreePage = 0; return NO_ERROR; } //*************************************************************************** // // CPageSource::WriteAdminPage // // Rewrites the admin page. There is no need to update the pagesize, // version, etc. // //*************************************************************************** // ok DWORD CPageSource::WriteAdminPage() { LPDWORD pPageZero = 0; DWORD dwRes = GetPage(0, (LPVOID *) &pPageZero); if (dwRes) return dwRes; pPageZero[OFFSET_LOGICAL_ROOT] = m_dwLogicalRoot; pPageZero[OFFSET_TOTAL_PAGES] = m_dwTotalPages; pPageZero[OFFSET_FREE_LIST_ROOT] = m_dwNextFreePage; dwRes = PutPage(pPageZero, PAGE_TYPE_ADMIN); _BtrMemFree(pPageZero); return dwRes; } //*************************************************************************** // // CPageSource::SetRootPage // //*************************************************************************** // DWORD CPageSource::SetRootPage(DWORD dwNewRoot) { m_dwLogicalRoot = dwNewRoot; return WriteAdminPage(); } //*************************************************************************** // // CPageSource::Init // // The real "constructor" which opens the file // //*************************************************************************** // ok DWORD CPageSource::Init( DWORD dwPageSize, LPWSTR pszFilename #ifdef A51_STAGE_BELOW_INDEX , CAbstractFileSource* pSource #endif ) { DWORD dwLastError = 0; m_dwPageSize = dwPageSize; DWORD dwFileFlags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; m_hFile = CreateFileW(pszFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, dwFileFlags, 0); dwLastError = GetLastError(); if (m_hFile == INVALID_HANDLE_VALUE) { if (dwLastError != ERROR_FILE_NOT_FOUND) return dwLastError; // If here, we have to create a new file. m_hFile = CreateFileW(pszFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, dwFileFlags, 0); if (m_hFile == INVALID_HANDLE_VALUE) return GetLastError(); #ifdef A51_STAGE_BELOW_INDEX long lRes = pSource->Register(m_hFile, A51_INDEX_FILE_ID, true, &m_pFile); if(lRes != ERROR_SUCCESS) { CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; return lRes; } #endif // Set up initial two pages Setup(); } else { // File exists. Verify that the file is an integral number of pages based // on the specified page size. DWORD dwLen = SetFilePointer(m_hFile, 0, 0, FILE_END); if (dwLen % dwPageSize != 0) { CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; return ERROR_BAD_FORMAT; } #ifdef A51_STAGE_BELOW_INDEX long lRes = pSource->Register(m_hFile, A51_INDEX_FILE_ID, true, &m_pFile); if(lRes != ERROR_SUCCESS) { CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; return lRes; } #endif } return ReadAdminPage(); } //*************************************************************************** // // CPageSource::ReadAdminPage // //*************************************************************************** // ok DWORD CPageSource::ReadAdminPage() { LPDWORD pPageZero = 0; DWORD dwRes = 0; dwRes = GetPage(0, (LPVOID *) &pPageZero); if (dwRes) return dwRes; // Grab useful info from these amazing admin pages... m_dwLogicalRoot = pPageZero[OFFSET_LOGICAL_ROOT]; m_dwTotalPages = pPageZero[OFFSET_TOTAL_PAGES]; m_dwNextFreePage = pPageZero[OFFSET_FREE_LIST_ROOT]; if (m_dwPageSize != pPageZero[OFFSET_PAGE_SIZE]) dwRes = ERROR_BAD_FORMAT; else if (pPageZero[OFFSET_IMPL_VERSION] != const_CurrentVersion) dwRes = ERROR_BAD_FORMAT; else dwRes = NO_ERROR; _BtrMemFree(pPageZero); return dwRes; } //*************************************************************************** // // CPageSource::Setup // // Sets up the 0th page (Admin page) // //*************************************************************************** // ok DWORD CPageSource::Setup() { DWORD dwRes; // First two pages, admin & free list root LPDWORD pPageZero = (LPDWORD) _BtrMemAlloc(m_dwPageSize); if (pPageZero == 0) { dwRes = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } memset(pPageZero, 0, m_dwPageSize); // Map the page pPageZero[OFFSET_PAGE_TYPE] = PAGE_TYPE_ADMIN; pPageZero[OFFSET_PAGE_ID] = 0; pPageZero[OFFSET_NEXT_PAGE] = 0; pPageZero[OFFSET_LOGICAL_ROOT] = 0; pPageZero[OFFSET_FREE_LIST_ROOT] = 0; pPageZero[OFFSET_TOTAL_PAGES] = 1; pPageZero[OFFSET_PAGE_SIZE] = m_dwPageSize; pPageZero[OFFSET_IMPL_VERSION] = const_CurrentVersion; // Write it out dwRes = PutPage(pPageZero, PAGE_TYPE_ADMIN); if (dwRes) goto Exit; m_dwTotalPages = 1; Exit: _BtrMemFree(pPageZero); return dwRes; } //*************************************************************************** // // CPageSource::Dump // // Debug helper // //*************************************************************************** // ok void CPageSource::Dump(FILE *f) { SetFilePointer(m_hFile, 0, 0, FILE_BEGIN); LPDWORD pPage = (LPDWORD) new BYTE[m_dwPageSize]; DWORD dwPage = 0; DWORD dwTotalKeys = 0; fprintf(f, "---BEGIN PAGE SOURCE DUMP---\n"); fprintf(f, "In memory part:\n"); fprintf(f, " m_dwPageSize = %d (0x%X)\n", m_dwPageSize, m_dwPageSize); fprintf(f, " m_hFile = 0x%p\n", m_hFile); fprintf(f, " m_dwNextFreePage = %d\n", m_dwNextFreePage); fprintf(f, " m_dwTotalPages = %d\n", m_dwTotalPages); fprintf(f, " m_dwLogicalRoot = %d\n", m_dwLogicalRoot); fprintf(f, "---\n"); DWORD dwTotalFree = 0; DWORD dwOffs = 0; while (1) { DWORD dwRead = 0; BOOL bRes = ReadFile(m_hFile, pPage, m_dwPageSize, &dwRead, 0); if (dwRead != m_dwPageSize) break; fprintf(f, "Dump of page %d:\n", dwPage++); fprintf(f, " Page type = 0x%X", pPage[OFFSET_PAGE_TYPE]); if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_IMPOSSIBLE) fprintf(f, " PAGE_TYPE_IMPOSSIBLE\n"); if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_DELETED) { fprintf(f, " PAGE_TYPE_DELETED\n"); fprintf(f, " \n", pPage[1]); fprintf(f, " \n", pPage[2]); dwTotalFree++; } if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_ACTIVE) { fprintf(f, " PAGE_TYPE_ACTIVE\n"); fprintf(f, " \n", pPage[1]); SIdxKeyTable *pKT = 0; DWORD dwKeys = 0; DWORD dwRes = SIdxKeyTable::Create(pPage, &pKT); if (dwRes == 0) { pKT->Dump(f, &dwKeys); pKT->Release(); dwTotalKeys += dwKeys; } else { fprintf(f, "\n"); } } if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_ADMIN) { fprintf(f, " PAGE_TYPE_ADMIN\n"); fprintf(f, " Page Num = %d\n", pPage[1]); fprintf(f, " Next Page = %d\n", pPage[2]); fprintf(f, " Logical Root = %d\n", pPage[3]); fprintf(f, " Free List Root = %d\n", pPage[4]); fprintf(f, " Total Pages = %d\n", pPage[5]); fprintf(f, " Page Size = %d (0x%X)\n", pPage[6], pPage[6]); fprintf(f, " Impl Version = 0x%X\n", pPage[7]); } } delete [] pPage; fprintf(f, "Total free pages detected by scan = %d\n", dwTotalFree); fprintf(f, "Total active keys = %d\n", dwTotalKeys); fprintf(f, "---END PAGE DUMP---\n"); } //*************************************************************************** // // CPageSource::GetPage // // Reads an existing page; does not support seeking beyond end-of-file // //*************************************************************************** // ok DWORD CPageSource::GetPage( DWORD dwPage, LPVOID *pPage ) { DWORD dwRes; if (pPage == 0) return ERROR_INVALID_PARAMETER; // Allocate some memory LPVOID pMem = _BtrMemAlloc(m_dwPageSize); if (!pMem) return ERROR_NOT_ENOUGH_MEMORY; // Where is this page hiding? DWORD dwOffs = dwPage * m_dwPageSize; DWORD dwRead = 0; #ifdef A51_STAGE_BELOW_INDEX long lRes = m_pFile->Read(dwOffs, pMem, m_dwPageSize, &dwRead); if (lRes != ERROR_SUCCESS) { _BtrMemFree(pMem); return lRes; } #else dwRes = SetFilePointer(m_hFile, dwOffs, 0, FILE_BEGIN); if (dwRes != dwOffs) { _BtrMemFree(pMem); return GetLastError(); } // Too late; the page cannot escape us BOOL bRes = ReadFile(m_hFile, pMem, m_dwPageSize, &dwRead, 0); if (!bRes || dwRead != m_dwPageSize) { _BtrMemFree(pMem); return GetLastError(); } #endif // Maybe this is a bogus page trying to pretend it is real // Check the signature *pPage = pMem; return NO_ERROR; } //*************************************************************************** // // CPageSource::PutPage // // Always rewrites; the file extent was grown when the page was allocated // with NewPage, so the page already exists and the write should not fail // //*************************************************************************** // ok DWORD CPageSource::PutPage( LPVOID pPage, DWORD dwType ) { // Force the page to confess its identity DWORD *pdwHeader = LPDWORD(pPage); DWORD dwPageId = pdwHeader[OFFSET_PAGE_ID]; pdwHeader[OFFSET_PAGE_TYPE] = dwType; // Where is it on the disk, we ask ourselves DWORD dwOffset = m_dwPageSize * dwPageId; DWORD dwWritten = 0; #ifdef A51_STAGE_BELOW_INDEX long lRes = m_pFile->Write(dwOffset, pPage, m_dwPageSize, &dwWritten); if(lRes != ERROR_SUCCESS) return lRes; #else DWORD dwRes = SetFilePointer(m_hFile, dwOffset, 0, FILE_BEGIN); if (dwRes != dwOffset) return GetLastError(); // Commit the page to the disk BOOL bRes = WriteFile(m_hFile, pPage, m_dwPageSize, &dwWritten, 0); if (bRes == FALSE) return GetLastError(); #endif // We're happy. But wait! Have we flushed this thing lately? // It would be a shame if the file buffers were much more enlightened // about the future than the disk itself. // =================================================================== DWORD dwNow = GetCurrentTime(); if (dwNow - m_dwLastFlushTime > MAX_FLUSH_INTERVAL) { Flush(); m_dwLastFlushTime = dwNow; } return NO_ERROR; } //*************************************************************************** // // CPageSource::NewPage // // Allocates a new page, preferring the free list // //*************************************************************************** // ok DWORD CPageSource::NewPage(LPVOID *pRetPage) { DWORD dwRes; if (pRetPage == 0) return ERROR_INVALID_PARAMETER; *pRetPage = 0; // Try to reuse a page from the "free list". Fast, fun, efficient. // ================================================================ if (m_dwNextFreePage) { LPDWORD pPage = 0; DWORD dwPage = m_dwNextFreePage; dwRes = GetPage(m_dwNextFreePage, (LPVOID *) &pPage); if (dwRes) return dwRes; m_dwNextFreePage = pPage[OFFSET_NEXT_PAGE]; dwRes = WriteAdminPage(); // Update free list if (dwRes) return dwRes; memset(pPage, 0, m_dwPageSize); pPage[OFFSET_PAGE_ID] = dwPage; *pRetPage = pPage; return NO_ERROR; } // Well, I guess there weren't any free pages left. Oh, well. Make a new one. // Move to the end of the file // ============================================================================ DWORD dwCurrentExtent = m_dwTotalPages * m_dwPageSize; DWORD dwNewExtent = dwCurrentExtent + m_dwPageSize; dwRes = SetFilePointer(m_hFile, dwNewExtent, 0, FILE_BEGIN); if (dwRes != dwNewExtent) { // Must be out of disk space. We're unhappy. return GetLastError(); } // Ahh... How are little file has grown. Now manufacture some // nonsensical memory with nothing in it and assign the page number & signatures. // ============================================================================== LPDWORD pNewPage = (LPDWORD) _BtrMemAlloc(m_dwPageSize); if (pNewPage == 0) return ERROR_NOT_ENOUGH_MEMORY; memset(pNewPage, 0, m_dwPageSize); pNewPage[OFFSET_PAGE_ID] = m_dwTotalPages++; *pRetPage = pNewPage; return WriteAdminPage(); } //*************************************************************************** // // CPageSource::FreePage // // Called to delete or free a page. If the last page is the one // being freed, then the file is truncated. // //*************************************************************************** // ok DWORD CPageSource::FreePage( LPVOID pPage ) { BOOL bRes; DWORD dwRes; LPDWORD pCast = LPDWORD(pPage); DWORD dwPageId = pCast[OFFSET_PAGE_ID]; if (dwPageId == 0 || m_dwLogicalRoot == dwPageId) { DebugBreak(); // Oops. Tried to delete the admin page or active root return ERROR_INVALID_PARAMETER; } // See if page is the last one. If so, simply truncate the // file and don't add to the free list. // ======================================================== #ifdef DONT_DO_THIS_ANY_MORE if (dwPageId == m_dwTotalPages - 1) { m_dwTotalPages--; DWORD dwTarget = dwPageId * m_dwPageSize; dwRes = SetFilePointer(m_hFile, dwTarget, 0, FILE_BEGIN); if (dwRes != dwTarget) return GetLastError(); bRes = SetEndOfFile(m_hFile); if (!bRes) return GetLastError(); return WriteAdminPage(); } #endif // Otherwise, it goes onto the good ol' free list. // =============================================== pCast[OFFSET_NEXT_PAGE] = m_dwNextFreePage; m_dwNextFreePage = dwPageId; dwRes = PutPage(pPage, PAGE_TYPE_DELETED); if (dwRes) return dwRes; dwRes = WriteAdminPage(); return dwRes; } //*************************************************************************** // // CPageSource::FreePage // //*************************************************************************** // DWORD CPageSource::FreePage( DWORD dwId ) { LPDWORD pFakePage = (LPDWORD) _BtrMemAlloc(m_dwPageSize); if (pFakePage == 0) return ERROR_NOT_ENOUGH_MEMORY; pFakePage[OFFSET_PAGE_TYPE] = PAGE_TYPE_ACTIVE; pFakePage[OFFSET_PAGE_ID] = dwId; DWORD dwRes = FreePage(pFakePage); _BtrMemFree(pFakePage); return dwRes; } //*************************************************************************** // // SIdxKeyTable::GetRequiredPageMemory // // Returns the amount of memory required to store this object in a // linear page // //*************************************************************************** // ok DWORD SIdxKeyTable::GetRequiredPageMemory() { DWORD dwTotal = m_pStrPool->GetRequiredPageMemory(); // Size of the key lookup table & its sizing DWORD, and // add in the child page & user data dwTotal += sizeof(DWORD) + sizeof(WORD) * m_dwNumKeys; dwTotal += sizeof(DWORD) + sizeof(DWORD) * m_dwNumKeys; // User data dwTotal += sizeof(DWORD) + sizeof(DWORD) * (m_dwNumKeys+1); // Child pages // Add in the key encoding table dwTotal += sizeof(WORD) + sizeof(WORD) * m_dwKeyCodesUsed; // Add in per page overhead // // Signature, Page Id, Next Page, Parent Page dwTotal += sizeof(DWORD) * 4; // (NOTE A): Add some safety margin... dwTotal += sizeof(DWORD) * 2; return dwTotal; } //*************************************************************************** // // SIdxKeyTable::StealKeyFromSibling // // Transfers a key from the sibling via the parent in a sort of rotation: // // 10 // 1 2 12 13 14 // // Where is node (1,2) and sibling is (12,13). A single rotation // moves 10 into (1,2) and grabs 12 from the sibling to replace it, // // 12 // 1 2 10 13 14 // // We repeat this until minimum load of is above const_MinimumLoad. // //*************************************************************************** // ok DWORD SIdxKeyTable::StealKeyFromSibling( SIdxKeyTable *pParent, SIdxKeyTable *pSibling ) { DWORD dwData, dwChild; WORD wID; LPSTR pszKey = 0; DWORD dwSiblingId = pSibling->GetPageId(); DWORD dwThisId = GetPageId(); for (WORD i = 0; i < WORD(pParent->GetNumKeys()); i++) { DWORD dwChildA = pParent->GetChildPage(i); DWORD dwChildB = pParent->GetChildPage(i+1); if (dwChildA == dwThisId && dwChildB == dwSiblingId) { pParent->GetKeyAt(i, &pszKey); dwData = pParent->GetUserData(i); FindKey(pszKey, &wID); AddKey(pszKey, wID, dwData); pParent->RemoveKey(i); _BtrMemFree(pszKey); pSibling->GetKeyAt(0, &pszKey); dwData = pSibling->GetUserData(0); dwChild = pSibling->GetChildPage(0); pSibling->RemoveKey(0); SetChildPage(wID+1, dwChild); pParent->AddKey(pszKey, i, dwData); pParent->SetChildPage(i, dwThisId); pParent->SetChildPage(i+1, dwSiblingId); _BtrMemFree(pszKey); break; } else if (dwChildA == dwSiblingId && dwChildB == dwThisId) { pParent->GetKeyAt(i, &pszKey); dwData = pParent->GetUserData(i); FindKey(pszKey, &wID); AddKey(pszKey, wID, dwData); pParent->RemoveKey(i); _BtrMemFree(pszKey); WORD wSibId = (WORD) pSibling->GetNumKeys() - 1; pSibling->GetKeyAt(wSibId, &pszKey); dwData = pSibling->GetUserData(wSibId); dwChild = pSibling->GetChildPage(wSibId+1); pSibling->RemoveKey(wSibId); SetChildPage(wID, dwChild); pParent->AddKey(pszKey, i, dwData); pParent->SetChildPage(i, dwSiblingId); pParent->SetChildPage(i+1, dwThisId); _BtrMemFree(pszKey); break; } } return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::Collapse // // Collapses the contents of a node and its sibling into just one // node and adjusts the parent. // // Precondition: The two siblings can be successfully collapsed // into a single node, accomodate a key migrated from the parent // and still safely fit into a single node. Page sizes are not // checked here. // //*************************************************************************** // ok DWORD SIdxKeyTable::Collapse( SIdxKeyTable *pParent, SIdxKeyTable *pDoomedSibling ) { WORD wId; DWORD dwRes; LPSTR pszKey = 0; DWORD dwData; DWORD dwChild; BOOL bExtra; DWORD dwSiblingId = pDoomedSibling->GetPageId(); DWORD dwThisId = GetPageId(); // Locate the node in the parent which points to the two // siblings. Since we don't know which sibling this is, // we have to take into account the two possibilites. // Is the right side or the left? // // 10 20 30 40 // | | | | | // x Sib This x x // // vs. // 10 20 30 40 // | | | | | // x This Sib x x // // We then migrate the key down into the current node // and remove it from the parent. We steal the first // // ====================================================== for (WORD i = 0; i < WORD(pParent->GetNumKeys()); i++) { DWORD dwChildA = pParent->GetChildPage(i); DWORD dwChildB = pParent->GetChildPage(i+1); if (dwChildA == dwSiblingId && dwChildB == dwThisId) { pParent->GetKeyAt(i, &pszKey); dwData = pParent->GetUserData(i); pParent->RemoveKey(i); pParent->SetChildPage(i, dwThisId); dwChild = pDoomedSibling->GetLastChildPage(); AddKey(pszKey, 0, dwData); SetChildPage(0, dwChild); _BtrMemFree(pszKey); bExtra = FALSE; break; } else if (dwChildA == dwThisId && dwChildB == dwSiblingId) { pParent->GetKeyAt(i, &pszKey); dwData = pParent->GetUserData(i); pParent->RemoveKey(i); pParent->SetChildPage(i, dwThisId); FindKey(pszKey, &wId); AddKey(pszKey, wId, dwData); _BtrMemFree(pszKey); bExtra = TRUE; break; } } // Move all info from sibling into the current node. // ================================================== DWORD dwNumSibKeys = pDoomedSibling->GetNumKeys(); for (WORD i = 0; i < WORD(dwNumSibKeys); i++) { LPSTR pKeyStr = 0; dwRes = pDoomedSibling->GetKeyAt(i, &pKeyStr); if (dwRes) return dwRes; DWORD dwUserData = pDoomedSibling->GetUserData(i); dwRes = FindKey(pKeyStr, &wId); if (dwRes != ERROR_NOT_FOUND) { _BtrMemFree(pKeyStr); return ERROR_BAD_FORMAT; } dwRes = AddKey(pKeyStr, wId, dwUserData); dwChild = pDoomedSibling->GetChildPage(i); SetChildPage(wId, dwChild); _BtrMemFree(pKeyStr); } if (bExtra) SetChildPage(WORD(GetNumKeys()), pDoomedSibling->GetLastChildPage()); pDoomedSibling->ZapPage(); return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::GetRightSiblingOf // SIdxKeyTable::GetRightSiblingOf // // Searches the child page pointers and returns the sibling of the // specified page. A return value of zero indicates there was not // sibling of the specified value in the direction requested. // //*************************************************************************** // DWORD SIdxKeyTable::GetRightSiblingOf( DWORD dwId ) { for (DWORD i = 0; i < m_dwNumKeys; i++) { if (m_pdwChildPageMap[i] == dwId) return m_pdwChildPageMap[i+1]; } return 0; } DWORD SIdxKeyTable::GetLeftSiblingOf( DWORD dwId ) { for (DWORD i = 1; i < m_dwNumKeys+1; i++) { if (m_pdwChildPageMap[i] == dwId) return m_pdwChildPageMap[i-1]; } return 0; } //*************************************************************************** // // SIdxKeyTable::Redist // // Used when inserting and performing a node split. // Precondition: // (a) The current node is oversized // (b) is ready to receive the new median key // (c) is completely empty and refers to the lesser node (left) // (d) All pages have assigned numbers // // We move the nodes from into the until both // are approximately half full. The median key is moved into the parent. // May fail if cannot allocate memory for the new stuff. // // If any errors occur, the entire sequence should be considered as failed // and the pages invalid. // //*************************************************************************** // DWORD SIdxKeyTable::Redist( SIdxKeyTable *pParent, SIdxKeyTable *pNewSibling ) { DWORD dwRes; WORD wID; if (pParent == 0 || pNewSibling == 0) return ERROR_INVALID_PARAMETER; if (m_dwNumKeys < 3) { return ERROR_INVALID_DATA; } // Find median key info and put it into parent. DWORD dwToTransfer = m_dwNumKeys / 2; while (dwToTransfer--) { // Get 0th key LPSTR pStr = 0; dwRes = GetKeyAt(0, &pStr); if (dwRes) return dwRes; DWORD dwUserData = GetUserData(0); // Move stuff into younger sibling dwRes = pNewSibling->FindKey(pStr, &wID); if (dwRes != ERROR_NOT_FOUND) { _BtrMemFree(pStr); return dwRes; } dwRes = pNewSibling->AddKey(pStr, wID, dwUserData); _BtrMemFree(pStr); if (dwRes) return dwRes; DWORD dwChildPage = GetChildPage(0); pNewSibling->SetChildPage(wID, dwChildPage); RemoveKey(0); } pNewSibling->SetChildPage(WORD(pNewSibling->GetNumKeys()), GetChildPage(0)); // Next key is the median key, which migrates to the parent. LPSTR pStr = 0; dwRes = GetKeyAt(0, &pStr); if (dwRes) return dwRes; DWORD dwUserData = GetUserData(0); dwRes = pParent->FindKey(pStr, &wID); if (dwRes != ERROR_NOT_FOUND) { _BtrMemFree(pStr); return dwRes; } dwRes = pParent->AddKey(pStr, wID, dwUserData); _BtrMemFree(pStr); if (dwRes) return dwRes; RemoveKey(0); // Patch in the various page pointers pParent->SetChildPage(wID, pNewSibling->GetPageId()); pParent->SetChildPage(wID+1, GetPageId()); // Everything else is already okay return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::SIdxKeyTable // //*************************************************************************** // ok SIdxKeyTable::SIdxKeyTable() { m_dwRefCount = 0; m_dwPageId = 0; m_dwParentPageId = 0; m_dwNumKeys = 0; // Num keys m_pwKeyLookup = 0; // Offset of key into key-lookup-table m_dwKeyLookupTotalSize = 0; // Elements in array m_pwKeyCodes = 0; // Key encoding table m_dwKeyCodesTotalSize = 0; // Total elements in array m_dwKeyCodesUsed = 0; // Elements used m_pStrPool = 0; // The pool associated with this key table m_pdwUserData = 0; // Stores user DWORDs for each key m_pdwChildPageMap = 0; // Stores the child page map (num keys + 1) } //*************************************************************************** // //*************************************************************************** // DWORD SIdxKeyTable::Clone( OUT SIdxKeyTable **pRetCopy ) { SIdxKeyTable *pCopy = new SIdxKeyTable; if (!pCopy) return ERROR_NOT_ENOUGH_MEMORY; pCopy->m_dwRefCount = 1; pCopy->m_dwPageId = m_dwPageId; pCopy->m_dwParentPageId = m_dwParentPageId; pCopy->m_dwNumKeys = m_dwNumKeys; pCopy->m_pwKeyLookup = (WORD *)_BtrMemAlloc(sizeof(WORD) * m_dwKeyLookupTotalSize); if (pCopy->m_pwKeyLookup == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(pCopy->m_pwKeyLookup, m_pwKeyLookup, sizeof(WORD) * m_dwKeyLookupTotalSize); pCopy->m_dwKeyLookupTotalSize = m_dwKeyLookupTotalSize; pCopy->m_pdwUserData = (DWORD *)_BtrMemAlloc(sizeof(DWORD) * m_dwKeyLookupTotalSize); if (pCopy->m_pdwUserData == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(pCopy->m_pdwUserData, m_pdwUserData, sizeof(DWORD) * m_dwKeyLookupTotalSize); pCopy->m_pdwChildPageMap = (DWORD *) _BtrMemAlloc(sizeof(DWORD) * (m_dwKeyLookupTotalSize+1)); if (pCopy->m_pdwChildPageMap == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(pCopy->m_pdwChildPageMap, m_pdwChildPageMap, sizeof(DWORD) * (m_dwKeyLookupTotalSize+1)); pCopy->m_dwKeyCodesTotalSize = m_dwKeyCodesTotalSize; pCopy->m_pwKeyCodes = (WORD *) _BtrMemAlloc(sizeof(WORD) * m_dwKeyCodesTotalSize); if (pCopy->m_pwKeyCodes == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(pCopy->m_pwKeyCodes, m_pwKeyCodes, sizeof(WORD)* m_dwKeyCodesTotalSize); pCopy->m_dwKeyCodesUsed = m_dwKeyCodesUsed; m_pStrPool->Clone(&pCopy->m_pStrPool); *pRetCopy = pCopy; return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::~SIdxKeyTable // //*************************************************************************** // SIdxKeyTable::~SIdxKeyTable() { if (m_pwKeyCodes) _BtrMemFree(m_pwKeyCodes); if (m_pwKeyLookup) _BtrMemFree(m_pwKeyLookup); if (m_pdwUserData) _BtrMemFree(m_pdwUserData); if (m_pdwChildPageMap) _BtrMemFree(m_pdwChildPageMap); if (m_pStrPool) delete m_pStrPool; } //*************************************************************************** // // SIdxKeyTable::GetKeyAt // // Precondition: is correct // The only real case of failure is that the return string cannot be allocated. // // Return values: // NO_ERROR // ERROR_NOT_ENOUGH_MEMORY // ERROR_INVALID_PARAMETER // //*************************************************************************** // tested DWORD SIdxKeyTable::GetKeyAt( WORD wID, LPSTR *pszKey ) { if (wID >= m_dwNumKeys || pszKey == 0) return ERROR_INVALID_PARAMETER; WORD wStartOffs = m_pwKeyLookup[wID]; WORD wNumTokens = m_pwKeyCodes[wStartOffs]; LPSTR Strings[MAX_TOKENS_PER_KEY]; DWORD dwTotalLengths = 0; for (DWORD i = 0; i < DWORD(wNumTokens); i++) { Strings[i] = m_pStrPool->GetStrById(m_pwKeyCodes[wStartOffs+1+i]); dwTotalLengths += strlen(Strings[i]); } LPSTR pszFinalStr = (LPSTR) _BtrMemAlloc(dwTotalLengths + 1 + wNumTokens); if (!pszFinalStr) return ERROR_NOT_ENOUGH_MEMORY; *pszFinalStr = 0; for (DWORD i = 0; i < DWORD(wNumTokens); i++) { if (i > 0) strcat(pszFinalStr, "\\"); strcat(pszFinalStr, Strings[i]); } *pszKey = pszFinalStr; return NO_ERROR; } //*************************************************************************** // // SIdxStringPool::FindStr // // Finds a string in the pool, if present and returns the assigned // offset. Uses a binary search. // // Return codes: // NO_ERROR The string was found // ERROR_NOT_FOND // //*************************************************************************** // tested DWORD SIdxStringPool::FindStr( IN LPSTR pszSearchKey, OUT WORD *pwStringNumber, OUT WORD *pwPoolOffset ) { if (m_dwNumStrings == 0) { *pwStringNumber = 0; return ERROR_NOT_FOUND; } // Binary search current node for key match. // ========================================= int nPosition = 0; int l = 0, u = int(m_dwNumStrings) - 1; while (l <= u) { int m = (l + u) / 2; // m is the current key to consider 0...n-1 LPSTR pszCandidateKeyStr = m_pStringPool+m_pwOffsets[m]; int nRes = strcmp(pszSearchKey, pszCandidateKeyStr); // Decide which way to cut the array in half. // ========================================== if (nRes < 0) { u = m - 1; nPosition = u + 1; } else if (nRes > 0) { l = m + 1; nPosition = l; } else { // If here, we found the darn thing. Life is good. // Populate the key unit. // ================================================ if (pwStringNumber) *pwStringNumber = WORD(m); if (pwPoolOffset) *pwPoolOffset = m_pwOffsets[m]; return NO_ERROR; } } // Not found, if here. We record where the key should have been // and tell the user the unhappy news. // ============================================================== *pwStringNumber = WORD(short(nPosition)); // The key would have been 'here' return ERROR_NOT_FOUND; } //*************************************************************************** // //*************************************************************************** // DWORD SIdxStringPool::Clone( SIdxStringPool **pRetCopy ) { SIdxStringPool *pCopy = new SIdxStringPool; if (pCopy == 0) return ERROR_NOT_ENOUGH_MEMORY; pCopy->m_dwNumStrings = m_dwNumStrings; pCopy->m_pwOffsets = (WORD *) _BtrMemAlloc(sizeof(WORD)*m_dwOffsetsSize); if (pCopy->m_pwOffsets == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(pCopy->m_pwOffsets, m_pwOffsets, sizeof(WORD)*m_dwOffsetsSize); pCopy->m_dwOffsetsSize = m_dwOffsetsSize; pCopy->m_pStringPool = (LPSTR) _BtrMemAlloc(m_dwPoolTotalSize); if (pCopy->m_pStringPool == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(pCopy->m_pStringPool, m_pStringPool, m_dwPoolTotalSize); pCopy->m_dwPoolTotalSize = m_dwPoolTotalSize; pCopy->m_dwPoolUsed = m_dwPoolUsed; *pRetCopy = pCopy; return NO_ERROR; } //*************************************************************************** // // SIdxStringPool::DeleteStr // // Removes a string from the pool and pool index. // Precondition: is known to be valid by virtue of a prior // call to . // // Return values: // NO_ERROR . // //*************************************************************************** // DWORD SIdxStringPool::DeleteStr( WORD wStringNum, int *pnAdjuster ) { if (pnAdjuster) *pnAdjuster = 0; // Find the address of the string to be removed. // ============================================= DWORD dwTargetOffs = m_pwOffsets[wStringNum]; LPSTR pszDoomed = m_pStringPool+dwTargetOffs; DWORD dwDoomedStrLen = strlen(pszDoomed) + 1; // Copy all subsequent strings over the top and shorten the heap. // Special case if this already the last string // ============================================================== DWORD dwStrBytesToMove = DWORD(m_pStringPool+m_dwPoolUsed - pszDoomed - dwDoomedStrLen); if (dwStrBytesToMove) memmove(pszDoomed, pszDoomed+dwDoomedStrLen, dwStrBytesToMove); m_dwPoolUsed -= dwDoomedStrLen; // Remove this entry from the array. // ================================= DWORD dwArrayElsToMove = m_dwNumStrings - wStringNum - 1; if (dwArrayElsToMove) { memmove(m_pwOffsets+wStringNum, m_pwOffsets+wStringNum+1, dwArrayElsToMove * sizeof(WORD)); if (pnAdjuster) *pnAdjuster = -1; } m_dwNumStrings--; // For all remaining elements, adjust offsets that were affected. // ============================================================== for (DWORD dwTrace = 0; dwTrace < m_dwNumStrings; dwTrace++) { if (m_pwOffsets[dwTrace] > dwTargetOffs) m_pwOffsets[dwTrace] -= WORD(dwDoomedStrLen); } // Adjust sizes. // ============= return NO_ERROR; } //*************************************************************************** // // SIdxStringPool::AddStr // // Adds a string to the pool. Assumes it is known prior to the call that // the string isn't present. // // Parameters: // pszString The string to add // pwAssignedOffset Returns the offset code assigned to the string // Return values: // NO_ERROR // ERROR_NOT_ENOUGH_MEMORY // //*************************************************************************** // ok DWORD SIdxStringPool::AddStr( LPSTR pszString, WORD wInsertPos, int *pnAdjuster ) { if (pnAdjuster) *pnAdjuster = 0; // Precondition: String doesn't exist in the table // Determine if the pool is too small for another string. // If so, extend it. // ====================================================== DWORD dwRequired = strlen(pszString)+1; DWORD dwPoolFree = m_dwPoolTotalSize - m_dwPoolUsed; if (m_dwPoolUsed + dwRequired - 1 > MAX_WORD_VALUE) { return ERROR_INSUFFICIENT_BUFFER; } if (dwRequired > dwPoolFree) { // Try to grow the pool // ==================== LPVOID pTemp = _BtrMemReAlloc(m_pStringPool, m_dwPoolTotalSize * 2); if (!pTemp) { return ERROR_NOT_ENOUGH_MEMORY; } m_dwPoolTotalSize *= 2; m_pStringPool = (LPSTR) pTemp; } // If array too small, reallocate to larger one // ============================================ if (m_dwNumStrings == m_dwOffsetsSize) { // Realloc; double current size LPVOID pTemp = _BtrMemReAlloc(m_pwOffsets, m_dwOffsetsSize * sizeof(WORD) * 2); if (!pTemp) return ERROR_NOT_ENOUGH_MEMORY; m_dwOffsetsSize *= 2; m_pwOffsets = PWORD(pTemp); } // If here, no problem. We have enough space for everything. // ========================================================= LPSTR pszInsertAddr = m_pStringPool+m_dwPoolUsed; DWORD dwInsertOffs = m_dwPoolUsed; strcpy(pszInsertAddr, pszString); m_dwPoolUsed += dwRequired; // If here, there is enough room. // ============================== DWORD dwToBeMoved = m_dwNumStrings - wInsertPos; if (dwToBeMoved) { memmove(&m_pwOffsets[wInsertPos+1], &m_pwOffsets[wInsertPos], sizeof(WORD)*dwToBeMoved); if (pnAdjuster) *pnAdjuster = 1; } m_pwOffsets[wInsertPos] = WORD(dwInsertOffs); m_dwNumStrings++; return NO_ERROR; } //*************************************************************************** // // ParseIntoTokens // // Parses a slash separated string into separate tokens in preparation // for encoding into the string pool. Call FreeStringArray on the output // when no longer needed. // // No more than MAX_TOKEN_PER_KEY are supported. This means that // if backslashes are used, no more than MAX_TOKEN_PER_KEY units can // be parsed out. // // Returns: // ERROR_INVALID_PARAMETER // ERROR_NOT_ENOUGH_MEMORY // NO_ERROR // //*************************************************************************** // ok DWORD ParseIntoTokens( IN LPSTR pszSource, OUT DWORD *pdwTokenCount, OUT LPSTR **pszTokens ) { LPSTR Strings[MAX_TOKENS_PER_KEY]; DWORD dwParseCount = 0, i = 0; DWORD dwSourceLen = strlen(pszSource); LPSTR *pszRetStr = 0; DWORD dwRet; if (pszSource == 0 || *pszSource == 0) return ERROR_INVALID_PARAMETER; LPSTR pszTempBuf = (LPSTR) _BtrMemAlloc(dwSourceLen+1); if (!pszTempBuf) return ERROR_NOT_ENOUGH_MEMORY; LPSTR pszTracer = pszTempBuf; for (;;) { *pszTracer = *pszSource; if (*pszTracer == '\\' || *pszTracer == 0) { *pszTracer = 0; // Replace with null terminator LPSTR pszTemp2 = (LPSTR) _BtrMemAlloc(strlen(pszTempBuf)+1); if (pszTemp2 == 0) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Error; } if (dwParseCount == MAX_TOKENS_PER_KEY) { _BtrMemFree(pszTemp2); dwRet = ERROR_INVALID_DATA; goto Error; } strcpy(pszTemp2, pszTempBuf); Strings[dwParseCount++] = pszTemp2; pszTracer = pszTempBuf; pszTracer--; } if (*pszSource == 0) break; pszTracer++; pszSource++; } // If here, we at least parsed one string. // ======================================= pszRetStr = (LPSTR *) _BtrMemAlloc(sizeof(LPSTR) * dwParseCount); if (pszRetStr == 0) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Error; } memcpy(pszRetStr, Strings, sizeof(LPSTR) * dwParseCount); *pdwTokenCount = dwParseCount; *pszTokens = pszRetStr; _BtrMemFree(pszTempBuf); return NO_ERROR; Error: for (i = 0; i < dwParseCount; i++) _BtrMemFree(Strings[i]); *pdwTokenCount = 0; _BtrMemFree(pszTempBuf); return dwRet; } //*************************************************************************** // // FreeTokenArray // // Cleans up the array returned by ParseIntoTokens. // //*************************************************************************** // ok void FreeTokenArray( DWORD dwCount, LPSTR *pszStrings ) { for (DWORD i = 0; i < dwCount; i++) _BtrMemFree(pszStrings[i]); _BtrMemFree(pszStrings); } //*************************************************************************** // // SIdxKeyTable::ZapPage // // Empties the page completely of all keys, codes, strings // //*************************************************************************** // ok void SIdxKeyTable::ZapPage() { m_pStrPool->Empty(); m_dwKeyCodesUsed = 0; m_dwNumKeys = 0; } //*************************************************************************** // // SIdxKeyTable::MapFromPage // // CAUTION!!! // The placement of DWORDs and WORDs is arranged to avoid 64-bit // alignment faults. // //*************************************************************************** // ok DWORD SIdxKeyTable::MapFromPage(LPVOID pSrc) { if (pSrc == 0) return ERROR_INVALID_PARAMETER; // Header // // DWORD[0] Signature // DWORD[1] Page number // DWORD[2] Next Page (always zero) // ==================================\ LPDWORD pDWCast = (LPDWORD) pSrc; if (*pDWCast++ != CPageSource::PAGE_TYPE_ACTIVE) { return ERROR_BAD_FORMAT; } m_dwPageId = *pDWCast++; pDWCast++; // Skip the 'next page' field // Key lookup table info // // DWORD[0] Parent Page // DWORD[1] Num Keys = n // DWORD[n] User Data // DWORD[n+1] Child Page Map // WORD[n] Key encoding offsets array // ====================================== m_dwParentPageId = *pDWCast++; m_dwNumKeys = *pDWCast++; // Decide the allocation sizes and build the arrays // ================================================ if (m_dwNumKeys <= const_DefaultArray) m_dwKeyLookupTotalSize = const_DefaultArray; else m_dwKeyLookupTotalSize = m_dwNumKeys; m_pdwUserData = (DWORD*) _BtrMemAlloc(m_dwKeyLookupTotalSize * sizeof(DWORD)); m_pdwChildPageMap = (DWORD*) _BtrMemAlloc((m_dwKeyLookupTotalSize+1) * sizeof(DWORD)); m_pwKeyLookup = (WORD*) _BtrMemAlloc(m_dwKeyLookupTotalSize * sizeof(WORD)); if (m_pdwUserData == 0 || m_pdwChildPageMap == 0 || m_pwKeyLookup == 0) { return ERROR_NOT_ENOUGH_MEMORY; } // Copy the page info into the arrays // ================================== memcpy(m_pdwUserData, pDWCast, sizeof(DWORD) * m_dwNumKeys); pDWCast += m_dwNumKeys; memcpy(m_pdwChildPageMap, pDWCast, sizeof(DWORD) * (m_dwNumKeys+1)); pDWCast += m_dwNumKeys + 1; memcpy(m_pwKeyLookup, pDWCast, sizeof(WORD) * m_dwNumKeys); LPWORD pWCast = LPWORD(pDWCast); pWCast += m_dwNumKeys; // Key encoding table info // // WORD[0] Num key codes = n // WORD[n] Key codes // =========================== m_dwKeyCodesUsed = (DWORD) *pWCast++; if (m_dwKeyCodesUsed <= const_DefaultKeyCodeArray) m_dwKeyCodesTotalSize = const_DefaultKeyCodeArray; else m_dwKeyCodesTotalSize = m_dwKeyCodesUsed; m_pwKeyCodes = (WORD*) _BtrMemAlloc(m_dwKeyCodesTotalSize * sizeof(WORD)); if (!m_pwKeyCodes) { return ERROR_NOT_ENOUGH_MEMORY; } memcpy(m_pwKeyCodes, pWCast, sizeof(WORD) * m_dwKeyCodesUsed); pWCast += m_dwKeyCodesUsed; // String pool // // WORD[0] Num strings = n // WORD[n] Offsets // // WORD[0] String pool size = n // BYTE[n] String pool // ============================= m_pStrPool = new SIdxStringPool; if (!m_pStrPool) return ERROR_NOT_ENOUGH_MEMORY; m_pStrPool->m_dwNumStrings = (DWORD) *pWCast++; if (m_pStrPool->m_dwNumStrings <= const_DefaultArray) m_pStrPool->m_dwOffsetsSize = const_DefaultArray; else m_pStrPool->m_dwOffsetsSize = m_pStrPool->m_dwNumStrings; m_pStrPool->m_pwOffsets = (WORD *) _BtrMemAlloc(sizeof(WORD)* m_pStrPool->m_dwOffsetsSize); if (m_pStrPool->m_pwOffsets == 0) return ERROR_NOT_ENOUGH_MEMORY; memcpy(m_pStrPool->m_pwOffsets, pWCast, sizeof(WORD)*m_pStrPool->m_dwNumStrings); pWCast += m_pStrPool->m_dwNumStrings; // String pool setup // ================= m_pStrPool->m_dwPoolUsed = *pWCast++; LPSTR pszCast = LPSTR(pWCast); if (m_pStrPool->m_dwPoolUsed <= SIdxStringPool::const_DefaultPoolSize) m_pStrPool->m_dwPoolTotalSize = SIdxStringPool::const_DefaultPoolSize; else m_pStrPool->m_dwPoolTotalSize = m_pStrPool->m_dwPoolUsed; m_pStrPool->m_pStringPool = (LPSTR) _BtrMemAlloc(m_pStrPool->m_dwPoolTotalSize); if (m_pStrPool->m_pStringPool == 0) { return ERROR_NOT_ENOUGH_MEMORY; } memcpy(m_pStrPool->m_pStringPool, pszCast, m_pStrPool->m_dwPoolUsed); return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::MapToPage // // Copies the info to a linear page. Precondition: the page must // be large enough by validating using a prior test to GetRequiredPageMemory. // //*************************************************************************** // ok DWORD SIdxKeyTable::MapToPage(LPVOID pDest) { if (pDest == 0) return ERROR_INVALID_PARAMETER; // Header // // DWORD[0] Signature // DWORD[1] Page number // DWORD[2] Next Page (always zero) // ==================================\ LPDWORD pDWCast = (LPDWORD) pDest; *pDWCast++ = CPageSource::PAGE_TYPE_ACTIVE; *pDWCast++ = m_dwPageId; *pDWCast++ = 0; // Unused 'next page' field // Key lookup table info // // DWORD[0] Parent Page // DWORD[1] Num Keys = n // DWORD[n] User Data // DWORD[n+1] Child Page Map // WORD[n] Key encoding offsets array // ====================================== *pDWCast++ = m_dwParentPageId; *pDWCast++ = m_dwNumKeys; // Decide the allocation sizes and build the arrays // ================================================ memcpy(pDWCast, m_pdwUserData, sizeof(DWORD) * m_dwNumKeys); pDWCast += m_dwNumKeys; memcpy(pDWCast, m_pdwChildPageMap, sizeof(DWORD) * (m_dwNumKeys+1)); pDWCast += m_dwNumKeys + 1; memcpy(pDWCast, m_pwKeyLookup, sizeof(WORD) * m_dwNumKeys); LPWORD pWCast = LPWORD(pDWCast); pWCast += m_dwNumKeys; // Key encoding table info // // WORD[0] Num key codes = n // WORD[n] Key codes // =========================== *pWCast++ = WORD(m_dwKeyCodesUsed); memcpy(pWCast, m_pwKeyCodes, sizeof(WORD) * m_dwKeyCodesUsed); pWCast += m_dwKeyCodesUsed; // String pool // // WORD[0] Num strings = n // WORD[n] Offsets // // WORD[0] String pool size = n // BYTE[n] String pool // ============================= *pWCast++ = WORD(m_pStrPool->m_dwNumStrings); memcpy(pWCast, m_pStrPool->m_pwOffsets, sizeof(WORD)*m_pStrPool->m_dwNumStrings); pWCast += m_pStrPool->m_dwNumStrings; *pWCast++ = WORD(m_pStrPool->m_dwPoolUsed); LPSTR pszCast = LPSTR(pWCast); memcpy(pszCast, m_pStrPool->m_pStringPool, m_pStrPool->m_dwPoolUsed); return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::Create // // Does a default create // //*************************************************************************** // ok DWORD SIdxKeyTable::Create( DWORD dwPageId, OUT SIdxKeyTable **pNewInst ) { SIdxKeyTable *p = new SIdxKeyTable; if (!p) return ERROR_NOT_ENOUGH_MEMORY; // Set up default string pool, arrays, etc. // ======================================== p->m_dwPageId = dwPageId; p->m_dwNumKeys = 0; p->m_pwKeyLookup = (WORD*) _BtrMemAlloc(const_DefaultArray * sizeof(WORD)); p->m_dwKeyLookupTotalSize = const_DefaultArray; p->m_pwKeyCodes = (WORD*) _BtrMemAlloc(const_DefaultArray * sizeof(WORD)); p->m_dwKeyCodesTotalSize = const_DefaultArray; p->m_dwKeyCodesUsed = 0; p->m_pdwUserData = (DWORD*) _BtrMemAlloc(const_DefaultArray * sizeof(DWORD)); p->m_pdwChildPageMap = (DWORD*) _BtrMemAlloc((const_DefaultArray+1) * sizeof(DWORD)); // Set up string pool. // =================== p->m_pStrPool = new SIdxStringPool; p->m_pStrPool->m_pwOffsets = (WORD*) _BtrMemAlloc(const_DefaultArray * sizeof(WORD)); p->m_pStrPool->m_dwOffsetsSize = const_DefaultArray; p->m_pStrPool->m_pStringPool = (LPSTR) _BtrMemAlloc(SIdxStringPool::const_DefaultPoolSize); p->m_pStrPool->m_dwPoolTotalSize = SIdxStringPool::const_DefaultPoolSize; // Check all pointers. If any are null, error out. // ================================================ if ( p->m_pwKeyLookup == NULL || p->m_pwKeyCodes == NULL || p->m_pdwUserData == NULL || p->m_pdwChildPageMap == NULL || p->m_pStrPool == NULL || p->m_pStrPool->m_pwOffsets == NULL || p->m_pStrPool->m_pStringPool == NULL ) { delete p; *pNewInst = 0; return ERROR_NOT_ENOUGH_MEMORY; } // Return good object to caller. // ============================= p->AddRef(); *pNewInst = p; return NO_ERROR; } //*************************************************************************** // // SIdxStringPool::~SIdxStringPool // //*************************************************************************** // ok SIdxStringPool::~SIdxStringPool() { if (m_pwOffsets) _BtrMemFree(m_pwOffsets); m_pwOffsets = 0; if (m_pStringPool) _BtrMemFree(m_pStringPool); // Pointer to string pool m_pStringPool = 0; } //*************************************************************************** // // SIdxKeyTable::Create // // Does a default create // //*************************************************************************** // ok DWORD SIdxKeyTable::Create( IN LPVOID pPage, OUT SIdxKeyTable **pNewInst ) { SIdxKeyTable *p = new SIdxKeyTable; if (!p) return ERROR_NOT_ENOUGH_MEMORY; DWORD dwRes = p->MapFromPage(pPage); if (dwRes) { *pNewInst = 0; return dwRes; } p->AddRef(); *pNewInst = p; return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::AddRef // //*************************************************************************** // ok DWORD SIdxKeyTable::AddRef() { InterlockedIncrement((LONG *) &m_dwRefCount); return m_dwRefCount; } //*************************************************************************** // // SIdxKeyTable::Release // //*************************************************************************** // ok DWORD SIdxKeyTable::Release() { DWORD dwNewCount = InterlockedDecrement((LONG *) &m_dwRefCount); if (0 != dwNewCount) return dwNewCount; delete this; return 0; } //*************************************************************************** // // SIdxKeyTable::Cleanup // // Does a consistency check of the key encoding table and cleans up the // string pool if any strings aren't being referenced. // //*************************************************************************** // ok DWORD SIdxKeyTable::Cleanup() { // See if all string pool codes are used in key code table. // If not, remove the string pool code. // ======================================================= DWORD dwLastId = m_pStrPool->GetLastId(); BOOL *pCheck = (BOOL*) _BtrMemAlloc(sizeof(BOOL) * dwLastId); if (!pCheck) return ERROR_NOT_ENOUGH_MEMORY; while (1) { if (m_pStrPool->GetNumStrings() == 0 || m_dwKeyCodesUsed == 0 || m_dwNumKeys == 0) { ZapPage(); break; } dwLastId = m_pStrPool->GetLastId(); memset(pCheck, 0, sizeof(BOOL)*dwLastId); // Mark all codes as 'unused' // Move through all key codes. If we delete a key encoding, there // may be a code in the string pool not used by the encoding. // What we have to do is set the pCheck array to TRUE for each // code encountered. If any have FALSE when we are done, we have // an unused code. WORD wCurrentSequence = 0; for (DWORD i = 0; i < m_dwKeyCodesUsed; i++) { if (wCurrentSequence == 0) // Skip the length WORD { wCurrentSequence = m_pwKeyCodes[i]; continue; } else // A string pool code pCheck[m_pwKeyCodes[i]] = TRUE; wCurrentSequence--; } // Now the pCheck array contains deep within its psyche // the knowledge of whether or not all string pool codes // were used TRUE for referenced ones, FALSE for those // not referenced. Let's look through it and see! DWORD dwUsed = 0, dwUnused = 0; for (i = 0; i < dwLastId; i++) { if (pCheck[i] == FALSE) { dwUnused++; // Yikes! A lonely, unused string code. Let's be merciful // and zap it before it knows the difference. // ======================================================= int nAdj = 0; m_pStrPool->DeleteStr(WORD(i), &nAdj); AdjustKeyCodes(WORD(i), nAdj); break; } else dwUsed++; } if (dwUnused == 0) break; } _BtrMemFree(pCheck); return NO_ERROR; } //*************************************************************************** // // SIdxKeyTable::AdjustKeyCodes // //*************************************************************************** // ok void SIdxKeyTable::AdjustKeyCodes( WORD wID, int nAdjustment ) { // Adjust all key codes starting with wID by the amount of the // adjustment, skipping length bytes. // ============================================================= WORD wCurrentSequence = 0; for (DWORD i = 0; i < m_dwKeyCodesUsed; i++) { if (wCurrentSequence == 0) { wCurrentSequence = m_pwKeyCodes[i]; continue; } else { if (m_pwKeyCodes[i] >= wID) m_pwKeyCodes[i] = m_pwKeyCodes[i] + nAdjustment; } wCurrentSequence--; } } //*************************************************************************** // // SIdxKeyTable::AddKey // // Adds a string to the table at position . Assumes FindString // was called first to get the correct location. // // Precondition: is valid, and is correct. // // Return codes: // // ERROR_OUT_OF_MEMORY // NO_ERROR // ERROR_INVALID_PARAMETER // Too many slashes in key // //*************************************************************************** // ok DWORD SIdxKeyTable::AddKey( LPSTR pszStr, WORD wKeyID, DWORD dwUserData ) { DWORD dwRes, dwRet; LPVOID pTemp = 0; LPSTR pszTemp = 0; DWORD dwLen, i; DWORD dwTokenCount = 0; WORD *pwTokenIDs = 0; DWORD dwNumNewTokens = 0; LPSTR *pszStrings = 0; DWORD dwToBeMoved; DWORD dwStartingOffset; // Set up some temp working arrays. // ================================ if (!pszStr) return ERROR_INVALID_PARAMETER; dwLen = strlen(pszStr); if (dwLen == 0) return ERROR_INVALID_PARAMETER; pszTemp = (LPSTR) _BtrMemAlloc(dwLen+1); if (!pszTemp) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } // Ensure there is enough room. // ============================ if (m_dwKeyLookupTotalSize == m_dwNumKeys) { // Expand the array. DWORD dwNewSize = m_dwKeyLookupTotalSize * 2; pTemp = _BtrMemReAlloc(m_pwKeyLookup, dwNewSize * sizeof(WORD)); if (!pTemp) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } m_dwKeyLookupTotalSize = dwNewSize; m_pwKeyLookup = PWORD(pTemp); // Expand user data. pTemp = _BtrMemReAlloc(m_pdwUserData, dwNewSize * sizeof(DWORD)); if (!pTemp) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } m_pdwUserData = (DWORD *) pTemp; // Expand child page map. pTemp = _BtrMemReAlloc(m_pdwChildPageMap, (dwNewSize + 1) * sizeof(DWORD)); if (!pTemp) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } m_pdwChildPageMap = (DWORD *) pTemp; } // Parse the string into backslash separated tokens. // ================================================= dwRes = ParseIntoTokens(pszStr, &dwTokenCount, &pszStrings); if (dwRes) { dwRet = dwRes; goto Exit; } // Allocate an array to hold the IDs of the tokens in the string. // ============================================================== pwTokenIDs = (WORD *) _BtrMemAlloc(sizeof(WORD) * dwTokenCount); if (pwTokenIDs == 0) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } // Work through the tokens and add them to the pool & key encoding table. // ============================================================= for (i = 0; i < dwTokenCount; i++) { LPSTR pszTok = pszStrings[i]; // See if token exists, if not add it. // =================================== WORD wID = 0; dwRes = m_pStrPool->FindStr(pszTok, &wID, 0); if (dwRes == NO_ERROR) { // Found it pwTokenIDs[dwNumNewTokens++] = wID; } else if (dwRes == ERROR_NOT_FOUND) { int nAdjustment = 0; dwRes = m_pStrPool->AddStr(pszTok, wID, &nAdjustment); if (dwRes) { dwRet = dwRes; goto Exit; } // Adjust string IDs because of the addition. // All existing ones with the same ID or higher // must be adjusted upwards. if (nAdjustment) { AdjustKeyCodes(wID, nAdjustment); for (DWORD i2 = 0; i2 < dwNumNewTokens; i2++) { if (pwTokenIDs[i2] >= wID) pwTokenIDs[i2] = pwTokenIDs[i2] + nAdjustment; } } // Adjust current tokens to accomodate new pwTokenIDs[dwNumNewTokens++] = wID; } else { dwRet = dwRes; goto Exit; } } // Now we know the encodings. Add them to the key encoding table. // First make sure that there is enough room in the table. // =============================================================== if (m_dwKeyCodesTotalSize - m_dwKeyCodesUsed < dwNumNewTokens + 1) { DWORD dwNewSize = m_dwKeyCodesTotalSize * 2 + dwNumNewTokens + 1; PWORD pTemp = (PWORD) _BtrMemReAlloc(m_pwKeyCodes, dwNewSize * sizeof(WORD)); if (!pTemp) { dwRet = ERROR_NOT_ENOUGH_MEMORY; goto Exit; } m_pwKeyCodes = pTemp; m_dwKeyCodesTotalSize = dwNewSize; } dwStartingOffset = m_dwKeyCodesUsed; m_pwKeyCodes[m_dwKeyCodesUsed++] = (WORD) dwNumNewTokens; // First WORD is count of tokens for (i = 0; i < dwNumNewTokens; i++) // Encoded tokens m_pwKeyCodes[m_dwKeyCodesUsed++] = pwTokenIDs[i]; // Now, add in the new key lookup by inserting it into the array. // ============================================================== dwToBeMoved = m_dwNumKeys - wKeyID; if (dwToBeMoved) { memmove(&m_pwKeyLookup[wKeyID+1], &m_pwKeyLookup[wKeyID], sizeof(WORD)*dwToBeMoved); memmove(&m_pdwUserData[wKeyID+1], &m_pdwUserData[wKeyID], sizeof(DWORD)*dwToBeMoved); memmove(&m_pdwChildPageMap[wKeyID+1], &m_pdwChildPageMap[wKeyID], (sizeof(DWORD))*(dwToBeMoved+1)); } m_pwKeyLookup[wKeyID] = (WORD) dwStartingOffset; m_pdwUserData[wKeyID] = dwUserData; m_dwNumKeys++; dwRet = NO_ERROR; // Cleanup code. // ============= Exit: if (pszTemp) _BtrMemFree(pszTemp); FreeTokenArray(dwTokenCount, pszStrings); if (pwTokenIDs) _BtrMemFree(pwTokenIDs); return dwRet; } //*************************************************************************** // // SIdxKeyTable::RemoveKey // // Precondition: is the valid target // //*************************************************************************** // ok DWORD SIdxKeyTable::RemoveKey( WORD wID ) { // Find the key code sequence and remove it. // ========================================= WORD wKeyCodeStart = m_pwKeyLookup[wID]; DWORD dwToBeMoved = m_dwNumKeys - DWORD(wID) - 1; if (dwToBeMoved) { memmove(&m_pwKeyLookup[wID], &m_pwKeyLookup[wID+1], sizeof(WORD)*dwToBeMoved); memmove(&m_pdwUserData[wID], &m_pdwUserData[wID+1], sizeof(DWORD)*dwToBeMoved); memmove(&m_pdwChildPageMap[wID], &m_pdwChildPageMap[wID+1], sizeof(DWORD)*(dwToBeMoved+1)); } m_dwNumKeys--; // Zap the key encoding table to remove references to this key. // ============================================================ WORD wCount = m_pwKeyCodes[wKeyCodeStart]+1; dwToBeMoved = m_dwKeyCodesUsed - (wKeyCodeStart + wCount); if (dwToBeMoved) memmove(&m_pwKeyCodes[wKeyCodeStart], &m_pwKeyCodes[wKeyCodeStart + wCount], sizeof(WORD)*dwToBeMoved); m_dwKeyCodesUsed -= wCount; // Adjust all zapped key codes referenced by key lookup table. // =========================================================== for (DWORD i = 0; i < m_dwNumKeys; i++) { if (m_pwKeyLookup[i] >= wKeyCodeStart) m_pwKeyLookup[i] -= wCount; } // Now check the string pool & key encoding table for // unreferenced strings thanks to the above tricks // and clean up the mess left behind!! // ================================================== return Cleanup(); } //*************************************************************************** // // Compares the literal string in against the encoded // string at . Returns the same value as strcmp(). // // This is done by decoding the compressed string, token by token, and // comparing each character to that in the search string. // //*************************************************************************** // ok int SIdxKeyTable::KeyStrCompare( LPSTR pszSearchKey, WORD wID ) { LPSTR pszTrace = pszSearchKey; WORD dwEncodingOffs = m_pwKeyLookup[wID]; WORD wNumTokens = m_pwKeyCodes[dwEncodingOffs]; WORD wStrId = m_pwKeyCodes[++dwEncodingOffs]; LPSTR pszDecoded = m_pStrPool->GetStrById(wStrId); wNumTokens--; int nRes; while (1) { int nTraceChar = *pszTrace++; int nCodedChar = *pszDecoded++; if (nCodedChar == 0 && wNumTokens) { pszDecoded = m_pStrPool->GetStrById(m_pwKeyCodes[++dwEncodingOffs]); wNumTokens--; nCodedChar = '\\'; } nRes = nTraceChar - nCodedChar; if (nRes || (nTraceChar == 0 && nCodedChar == 0)) return nRes; } // Identical strings return 0; } //*************************************************************************** // // SIdxKeyTable::FindKey // // Finds the key in the key table, if present. If not, returns // ERROR_NOT_FOUND and set to the point where it would be if // later inserted. // // Return values: // ERROR_NOT_FOUND // NO_ERROR // //*************************************************************************** // ready for test DWORD SIdxKeyTable::FindKey( LPSTR pszSearchKey, WORD *pID ) { if (pszSearchKey == 0 || *pszSearchKey == 0 || pID == 0) return ERROR_INVALID_PARAMETER; // Binary search the key table. // ============================ if (m_dwNumKeys == 0) { *pID = 0; return ERROR_NOT_FOUND; } int nPosition = 0; int l = 0, u = int(m_dwNumKeys) - 1; while (l <= u) { int m = (l + u) / 2; int nRes; // m is the current key to consider 0...n-1 try { nRes = KeyStrCompare(pszSearchKey, WORD(m)); } catch(...) { DebugBreak(); nRes = KeyStrCompare(pszSearchKey, WORD(m)); } // Decide which way to cut the array in half. // ========================================== if (nRes < 0) { u = m - 1; nPosition = u + 1; } else if (nRes > 0) { l = m + 1; nPosition = l; } else { // If here, we found the darn thing. Life is good. // Populate the key unit. // ================================================ *pID = WORD(m); return NO_ERROR; } } // Not found, if here. We record where the key should have been // and tell the user the unhappy news. // ============================================================== *pID = WORD(nPosition); // The key would have been 'here' return ERROR_NOT_FOUND; } //*************************************************************************** // //*************************************************************************** // untested DWORD SIdxKeyTable::Dump(FILE *f, DWORD *pdwKeys) { fprintf(f, "\t|---Begin Key Table Dump---\n"); fprintf(f, "\t| m_dwPageId = %d (0x%X)\n", m_dwPageId, m_dwPageId); fprintf(f, "\t| m_dwParentPageId = %d\n", m_dwParentPageId); fprintf(f, "\t| m_dwNumKeys = %d\n", m_dwNumKeys); fprintf(f, "\t| m_pwKeyLookup = 0x%p\n", m_pwKeyLookup); fprintf(f, "\t| m_dwKeyLookupTotalSize = %d\n", m_dwKeyLookupTotalSize); fprintf(f, "\t| m_pwKeyCodes = 0x%p\n", m_pwKeyCodes); fprintf(f, "\t| m_dwKeyCodesTotalSize = %d\n", m_dwKeyCodesTotalSize); fprintf(f, "\t| m_dwKeyCodesUsed = %d\n", m_dwKeyCodesUsed); fprintf(f, "\t| Required Page Memory = %d\n", GetRequiredPageMemory()); fprintf(f, "\t| --Key Lookup Table\n"); if (pdwKeys) *pdwKeys = m_dwNumKeys; for (DWORD i = 0; i < m_dwNumKeys; i++) { fprintf(f, "\t| * Left Child Page ------------------------> %d\n", m_pdwChildPageMap[i]); fprintf(f, "\t| KeyID[%d] = offset %d\n", i, m_pwKeyLookup[i]); fprintf(f, "\t| User Data=%d\n", m_pdwUserData[i]); WORD wKeyEncodingOffset = m_pwKeyLookup[i]; WORD wEncodingUnits = m_pwKeyCodes[wKeyEncodingOffset]; int nPass = 0; fprintf(f, "\t | Key="); for (DWORD i2 = 0; i2 < DWORD(wEncodingUnits); i2++) { WORD wCode = m_pwKeyCodes[wKeyEncodingOffset + 1 + i2]; if (nPass) fprintf(f, "\\"); fprintf(f, "%s", m_pStrPool->GetStrById(wCode)); nPass++; } fprintf(f, "\n"); fprintf(f, "\t| Num encoding units = %d\n", wEncodingUnits); for (DWORD i2 = 0; i2 < DWORD(wEncodingUnits); i2++) { WORD wCode = m_pwKeyCodes[wKeyEncodingOffset + 1 + i2]; fprintf(f, "\t | KeyCode = %d\n", wCode); } } fprintf(f, "\t| * Rightmost child page -------------------> %d\n", m_pdwChildPageMap[i]); fprintf(f, "\t|---\n"); #ifdef EXTENDED_STRING_TABLE_DUMP fprintf(f, "\t|---Key Encoding Table\n"); WORD wCurrentSequence = 0; for (i = 0; i < m_dwKeyCodesUsed; i++) { if (wCurrentSequence == 0) { wCurrentSequence = m_pwKeyCodes[i]; fprintf(f, "\t| KeyCode[%d] = %d \n", i, m_pwKeyCodes[i]); continue; } else fprintf(f, "\t| KeyCode[%d] = %d <%s>\n", i, m_pwKeyCodes[i], m_pStrPool->GetStrById(m_pwKeyCodes[i])); wCurrentSequence--; } fprintf(f, "\t|---End Key Encoding Table---\n"); m_pStrPool->Dump(f); #endif return 0; } //*************************************************************************** // // SIdxStringPool::Dump // // Dumps the string pool // //*************************************************************************** // tested DWORD SIdxStringPool::Dump(FILE *f) { try { fprintf(f, "\t\t|| ---String Pool Dump---\n"); fprintf(f, "\t\t|| m_dwNumStrings = %d\n", m_dwNumStrings); fprintf(f, "\t\t|| m_pwOffsets = 0x%p\n", m_pwOffsets); fprintf(f, "\t\t|| m_dwOffsetsSize = %d\n", m_dwOffsetsSize); fprintf(f, "\t\t|| m_pStringPool = 0x%p\n", m_pStringPool); fprintf(f, "\t\t|| m_dwPoolTotalSize = %d\n", m_dwPoolTotalSize); fprintf(f, "\t\t|| m_dwPoolUsed = %d\n", m_dwPoolUsed); fprintf(f, "\t\t|| --Contents of offsets array--\n"); for (DWORD ix = 0; ix < m_dwNumStrings; ix++) { fprintf(f, "\t\t|| String[%d] = offset %d Value=<%s>\n", ix, m_pwOffsets[ix], m_pStringPool+m_pwOffsets[ix]); } #ifdef EXTENDED_STRING_TABLE_DUMP fprintf(f, "\t\t|| --String table--\n"); for (ix = 0; ix < m_dwPoolTotalSize; ix += 20) { fprintf(f, "\t\t || %4d ", ix); for (int nSubcount = 0; nSubcount < 20; nSubcount++) { if (nSubcount + ix >= m_dwPoolTotalSize) continue; char c = m_pStringPool[ix+nSubcount]; fprintf(f, "%02x ", c); } for (int nSubcount = 0; nSubcount < 20; nSubcount++) { if (nSubcount + ix >= m_dwPoolTotalSize) continue; char c = m_pStringPool[ix+nSubcount]; if (c < 32) { c = '.'; } fprintf(f, "%c ", c); } fprintf(f, "\n"); } #endif fprintf(f, "\t\t|| ---End of String Pool Dump\n"); } catch(...) { printf("Exception during dump\n"); } return 0; } //*************************************************************************** // // CBTree::Init // //*************************************************************************** // DWORD CBTree::Init( CPageSource *pSrc ) { DWORD dwRes; if (pSrc == 0) return ERROR_INVALID_PARAMETER; // Read the logical root page, if any. If the index is just // being created, create the root index page. m_pSrc = pSrc; m_pRoot = 0; DWORD dwRoot = m_pSrc->GetRootPage(); if (dwRoot == 0) { LPDWORD pNewPage = 0; dwRes = m_pSrc->NewPage((LPVOID *) &pNewPage); if (dwRes) return dwRes; DWORD dwPageNum = pNewPage[CPageSource::OFFSET_PAGE_ID]; _BtrMemFree(pNewPage); dwRes = SIdxKeyTable::Create(dwPageNum, &m_pRoot); if (dwRes) return dwRes; m_pSrc->SetRootPage(dwPageNum); dwRes = WriteIdxPage(m_pRoot); } else { // Retrieve existing root LPVOID pPage = 0; dwRes = m_pSrc->GetPage(dwRoot, &pPage); if (dwRes) return dwRes; dwRes = SIdxKeyTable::Create(pPage, &m_pRoot); _BtrMemFree(pPage); if (dwRes) return dwRes; } return dwRes; } //*************************************************************************** // // CBTree::CBTree // //*************************************************************************** // CBTree::CBTree() { m_pSrc = 0; m_pRoot = 0; m_lGeneration = 0; InitializeCriticalSection(&m_cs); CBTreeIterator *m_pIterators; DWORD m_dwActiveIterators; DWORD m_dwIteratorsArraySize; m_pIterators = 0; m_dwActiveIterators = 0; m_dwIteratorsArraySize = 0; } //*************************************************************************** // // CBTree::~CBTree // //*************************************************************************** // CBTree::~CBTree() { if (m_pSrc || m_pRoot) { Shutdown(WMIDB_SHUTDOWN_NET_STOP); } DeleteCriticalSection(&m_cs); } //*************************************************************************** // // CBTree::Shutdown // //*************************************************************************** // DWORD CBTree::Shutdown(DWORD dwShutDownFlags) { DWORD dwRes; if (m_pRoot) { m_pRoot->Release(); m_pRoot = 0; } if (m_pSrc) { dwRes = m_pSrc->Shutdown(dwShutDownFlags); m_pSrc = 0; return dwRes; } return ERROR_INVALID_FUNCTION; } //*************************************************************************** // // CBTree::InsertKey // // Inserts the key+data into the tree. Most of the work is done // in InsertPhase2(). // //*************************************************************************** // ok DWORD CBTree::InsertKey( IN LPSTR pszKey, DWORD dwValue ) { WORD wID; DWORD dwRes; SIdxKeyTable *pIdx = 0; LONG StackPtr = -1; DWORD Stack[CBTreeIterator::const_MaxStack]; if (pszKey == 0 || *pszKey == 0) return ERROR_INVALID_PARAMETER; dwRes = Search(pszKey, &pIdx, &wID, Stack, StackPtr); if (dwRes == 0) { // Ooops. Aleady exists. We can't insert it. // =========================================== pIdx->Release(); return ERROR_ALREADY_EXISTS; } if (dwRes != ERROR_NOT_FOUND) return dwRes; // If here, we can indeed add it. // ============================== dwRes = InsertPhase2(pIdx, wID, pszKey, dwValue, Stack, StackPtr); pIdx->Release(); return dwRes; } //*************************************************************************** // // CBTree::ComputeLoad // //*************************************************************************** // DWORD CBTree::ComputeLoad( SIdxKeyTable *pKT ) { DWORD dwMem = pKT->GetRequiredPageMemory(); DWORD dwLoad = dwMem * 100 / m_pSrc->GetPageSize(); return dwLoad; } //*************************************************************************** // // CBTree::Search // // The actual search occurs here. Descends through the page mechanism. // // Returns: // NO_ERROR is assigned, and points to the key. // // ERROR_NOT_FOUND is assigned to where the insert should occur, // at in that page. // // Other errors don't assign the OUT parameters. // // Note: caller must release using Release() when it is returned // whether with an error code or not. // //*************************************************************************** // ok DWORD CBTree::Search( IN LPSTR pszKey, OUT SIdxKeyTable **pRetIdx, OUT WORD *pwID, DWORD Stack[], LONG &StackPtr ) { DWORD dwRes, dwChildPage, dwPage; if (pszKey == 0 || *pszKey == 0 || pwID == 0 || pRetIdx == 0) return ERROR_INVALID_PARAMETER; *pRetIdx = 0; SIdxKeyTable *pIdx = m_pRoot; pIdx->AddRef(); Stack[++StackPtr] = 0; while (1) { dwRes = pIdx->FindKey(pszKey, pwID); if (dwRes == 0) { // Found it *pRetIdx = pIdx; return NO_ERROR; } // Otherwise, we have to try to descend to a child page. // ===================================================== dwPage = pIdx->GetPageId(); dwChildPage = pIdx->GetChildPage(*pwID); if (dwChildPage == 0) break; pIdx->Release(); pIdx = 0; Stack[++StackPtr] = dwPage; dwRes = ReadIdxPage(dwChildPage, &pIdx); if (dwRes) return dwRes; } *pRetIdx = pIdx; return ERROR_NOT_FOUND; } //*************************************************************************** // // CBTree::InsertPhase2 // // On entry, assumes that we have identified the page into which // the insert must physically occur. This does the split + migrate // logical to keep the tree in balance. // // Algorithm: Add key to page. If it does not overflow, we are done. // If overflow occurs, allocate a new sibling page which will acquire // half the keys from the current page. This sibling will be treated // as lexically smaller in all cases. The median key is migrated // up to the parent with pointers to both the new sibing page and // the current page. // The parent may also overflow. If so, the algorithm repeats. // If an overflow occurs and there is no parent node (we are at the root) // a new root node is allocated and the median key migrated into it. // //*************************************************************************** // ok DWORD CBTree::InsertPhase2( SIdxKeyTable *pCurrent, WORD wID, LPSTR pszKey, DWORD dwValue, DWORD Stack[], LONG &StackPtr ) { DWORD dwRes; // If non-NULL, used for a primary insert. // If NULL, skip this, under the assumption the // node is already up-to-date and merely requires // the up-recursive split & migrate treatment. // ============================================== if (pszKey) { dwRes = pCurrent->AddKey(pszKey, wID, dwValue); if (dwRes) return dwRes; // Failed } pCurrent->AddRef(); // Makes following loop consistent SIdxKeyTable *pSibling = 0; SIdxKeyTable *pParent = 0; // The class B-tree split+migration loop. // ====================================== for (;;) { // Check the current node where we added the key. // If it isn't too big, we're done. // ============================================== dwRes = pCurrent->GetRequiredPageMemory(); if (dwRes <= m_pSrc->GetPageSize()) { dwRes = WriteIdxPage(pCurrent); break; } // If here, it ain't gonna fit. We have to split the page. // Allocate a new page (Sibling) and get the parent page, which // will receive the median key. // ============================================================ DWORD dwParent = Stack[StackPtr--]; if (dwParent == 0) { // Allocate a new page to become the parent. LPDWORD pParentPg = 0; dwRes = m_pSrc->NewPage((LPVOID *) &pParentPg); if (dwRes) break; DWORD dwNewParent = pParentPg[CPageSource::OFFSET_PAGE_ID]; _BtrMemFree(pParentPg); dwRes = SIdxKeyTable::Create(dwNewParent, &pParent); if (dwRes) break; dwRes = m_pSrc->SetRootPage(dwNewParent); if (dwRes) break; m_pRoot->Release(); // Replace old root m_pRoot = pParent; m_pRoot->AddRef(); } else { if (dwParent == m_pRoot->GetPageId()) { pParent = m_pRoot; pParent->AddRef(); } else { dwRes = ReadIdxPage(dwParent, &pParent); if (dwRes) break; } } // Allocate a new sibling in any case to hold half the keys // ======================================================== LPDWORD pSibPg = 0; dwRes = m_pSrc->NewPage((LPVOID *) &pSibPg); if (dwRes) break; DWORD dwNewSib = pSibPg[CPageSource::OFFSET_PAGE_ID]; _BtrMemFree(pSibPg); dwRes = SIdxKeyTable::Create(dwNewSib, &pSibling); if (dwRes) break; dwRes = pCurrent->Redist(pParent, pSibling); if (dwRes) break; dwRes = WriteIdxPage(pCurrent); dwRes |= WriteIdxPage(pSibling); pCurrent->Release(); pCurrent = 0; pSibling->Release(); pSibling = 0; if (dwRes) break; pCurrent = pParent; pParent = 0; } ReleaseIfNotNULL(pParent); ReleaseIfNotNULL(pCurrent); ReleaseIfNotNULL(pSibling); return dwRes; } //*************************************************************************** // // CBTree::WriteIdxPage // // Writes the object to the physical page it is assigned to. // If the page ID is zero, then it is considered invalid. Further, // while is it correct to precheck the page size, this function does // validate with regard to sizes, etc. // //*************************************************************************** // DWORD CBTree::WriteIdxPage( SIdxKeyTable *pIdx ) { DWORD dwRes; DWORD dwPageSize = m_pSrc->GetPageSize(); DWORD dwMem = pIdx->GetRequiredPageMemory(); if (dwMem > dwPageSize) return ERROR_INVALID_PARAMETER; LPVOID pMem = _BtrMemAlloc(dwPageSize); if (pMem == 0) return ERROR_NOT_ENOUGH_MEMORY; dwRes = pIdx->MapToPage(pMem); if (dwRes) { _BtrMemFree(pMem); return dwRes; } dwRes = m_pSrc->PutPage(pMem, CPageSource::PAGE_TYPE_ACTIVE); _BtrMemFree(pMem); InterlockedIncrement(&m_lGeneration); // Check for a root update. // ======================== if (m_pRoot != pIdx && m_pRoot->GetPageId() == pIdx->GetPageId()) { m_pRoot->Release(); m_pRoot = pIdx; m_pRoot->AddRef(); if (m_pSrc->GetRootPage() != m_pRoot->GetPageId()) dwRes = m_pSrc->SetRootPage(m_pRoot->GetPageId()); } return dwRes; } //*************************************************************************** // // CBTree::ReadIdxPage // //*************************************************************************** // DWORD CBTree::ReadIdxPage( DWORD dwPage, SIdxKeyTable **pIdx ) { DWORD dwRes; LPVOID pPage = 0; SIdxKeyTable *p = 0; if (pIdx == 0) return ERROR_INVALID_PARAMETER; *pIdx = 0; // if (dwPage < MAX_PAGE_HISTORY) // May remove if studies show no caching possible // ++History[dwPage]; dwRes = m_pSrc->GetPage(dwPage, &pPage); if (dwRes) return dwRes; dwRes = SIdxKeyTable::Create(pPage, &p); if (dwRes) { _BtrMemFree(pPage); return dwRes; } _BtrMemFree(pPage); if (dwRes) return dwRes; *pIdx = p; return dwRes; } //*************************************************************************** // // CBTree::FindKey // // Does a simple search of a key, returning the user data, if requested. // // Typical Return values // NO_ERROR // ERROR_NOT_FOUND // //*************************************************************************** // ok DWORD CBTree::FindKey( IN LPSTR pszKey, DWORD *pdwData ) { WORD wID; DWORD dwRes; SIdxKeyTable *pIdx = 0; LONG StackPtr = -1; DWORD Stack[CBTreeIterator::const_MaxStack]; if (pszKey == 0 || *pszKey == 0) return ERROR_INVALID_PARAMETER; // Search high and low, hoping against hope... // =========================================== dwRes = Search(pszKey, &pIdx, &wID, Stack, StackPtr); if (dwRes == 0 && pdwData) { *pdwData = pIdx->GetUserData(wID); } // If here, we can indeed add it. // ============================== ReleaseIfNotNULL(pIdx); return dwRes; } //*************************************************************************** // // CBTree::DeleteKey // //*************************************************************************** // DWORD CBTree::DeleteKey( IN LPSTR pszKey ) { DWORD dwRes; LONG StackPtr = -1; DWORD Stack[CBTreeIterator::const_MaxStack]; SIdxKeyTable *pIdx = 0; WORD wId; DWORD dwLoad; // Find it // ======= dwRes = Search(pszKey, &pIdx, &wId, Stack, StackPtr); if (dwRes) return dwRes; // Delete key from from page // ========================== if (pIdx->IsLeaf()) { // A leaf node. Remove the key. // ============================= dwRes = pIdx->RemoveKey(wId); // Now, check the load and see if it has dropped below 30%. // Of course, if we are at the root node and it is a leaf, // we have to pretty much let it go as is... // ======================================================== dwLoad = ComputeLoad(pIdx); if (dwLoad > const_MinimumLoad || pIdx->GetPageId() == m_pRoot->GetPageId()) { dwRes = WriteIdxPage(pIdx); pIdx->Release(); return dwRes; } } else { // An internal node, so we have to find the successor. // Since this call may alter the shape of the tree quite // a bit (the successor may overflow the affected node), // we have to relocate the successor. // ==================================================== LPSTR pszSuccessor = 0; BOOL bUnderflow = FALSE; dwRes = ReplaceBySuccessor(pIdx, wId, &pszSuccessor, &bUnderflow, Stack, StackPtr); if (dwRes) return dwRes; dwRes = InsertPhase2(pIdx, 0, 0, 0, Stack, StackPtr); if (dwRes) return dwRes; pIdx->Release(); pIdx = 0; StackPtr = -1; if (bUnderflow == FALSE) { _BtrMemFree(pszSuccessor); return NO_ERROR; } // If here, the node we extracted the successor from was reduced // to poverty and underflowed. We have to find it again and // execute the underflow repair loop. // ============================================================= dwRes = Search(pszSuccessor, &pIdx, &wId, Stack, StackPtr); _BtrMemFree(pszSuccessor); if (dwRes) return dwRes; SIdxKeyTable *pSuccessor = 0; dwRes = FindSuccessorNode(pIdx, wId, &pSuccessor, 0, Stack, StackPtr); if (dwRes) return dwRes; pIdx->Release(); pIdx = pSuccessor; } // UNDERFLOW REPAIR Loop. // At this point points to the deepest affected node. // We need to start working back up the tree and repairing // the damage. Nodes which have reached zero in size are // quite a pain. But they aren't half as bad as nodes which claim // they can recombine with a sibling but really can't. So, // we either do nothing (the node has enough stuff to be useful), // collapse with a sibling node or borrow some keys from a sibling // to ensure all nodes meet the minimum load requirement. // =============================================================== SIdxKeyTable *pSibling = 0; SIdxKeyTable *pParent = 0; for (;;) { DWORD dwParentId = Stack[StackPtr--]; DWORD dwThisId = pIdx->GetPageId(); dwLoad = ComputeLoad(pIdx); if (dwLoad > const_MinimumLoad || dwParentId == 0) { dwRes = WriteIdxPage(pIdx); pIdx->Release(); break; } // If here the node is getting small. We must collapsed this // node with a sibling. // collapse this node and a sibling dwRes = ReadIdxPage(dwParentId, &pParent); // Locate a sibling and see if the sibling and the current node // can be collapsed with leftover space. // ============================================================= DWORD dwLeftSibling = pParent->GetLeftSiblingOf(pIdx->GetPageId()); DWORD dwRightSibling = pParent->GetRightSiblingOf(pIdx->GetPageId()); DWORD dwSiblingId = 0; if (dwLeftSibling) { dwRes = ReadIdxPage(dwLeftSibling, &pSibling); dwSiblingId = pSibling->GetPageId(); } else { dwRes = ReadIdxPage(dwRightSibling, &pSibling); dwSiblingId = pSibling->GetPageId(); } // If here, the node is 'underloaded'. Now we have to // get the parent and the sibling and collapsed them. // =================================================== SIdxKeyTable *pCopy = 0; pIdx->Clone(&pCopy); dwRes = pIdx->Collapse(pParent, pSibling); // Now we have a different sort of problem, possibly. // If the collapsed node is too big, we have to try // a different strategy. // =================================================== if (pIdx->GetRequiredPageMemory() > m_pSrc->GetPageSize()) { pIdx->Release(); pParent->Release(); pSibling->Release(); pIdx = pParent = pSibling = 0; // Reread the pages. // ================= pIdx = pCopy; dwRes = ReadIdxPage(dwParentId, &pParent); dwRes = ReadIdxPage(dwSiblingId, &pSibling); // Transfer a key or two from sibling via parent. // This doesn't change the tree shape, but the // parent may overflow. // ============================================== do { dwRes = pIdx->StealKeyFromSibling(pParent, pSibling); dwLoad = ComputeLoad(pIdx); } while (dwLoad < const_MinimumLoad); dwRes = WriteIdxPage(pIdx); pIdx->Release(); dwRes = WriteIdxPage(pSibling); pSibling->Release(); dwRes = InsertPhase2(pParent, 0, 0, 0, Stack, StackPtr); pParent->Release(); break; } else // The collapse worked; we can free the sibling page { pCopy->Release(); dwRes = m_pSrc->FreePage(pSibling->GetPageId()); pSibling->Release(); } // If here, the collapse worked. // ============================= dwRes = WriteIdxPage(pIdx); if (dwRes) { pIdx->Release(); break; } if (pParent->GetNumKeys() == 0) { // We have replaced the root. Note // that we transfer the ref count of pIdx to m_pRoot. DWORD dwOldRootId = m_pRoot->GetPageId(); m_pRoot->Release(); m_pRoot = pIdx; // Even though we wrote a few lines back, // a rewrite is required to update internal stuff // because this has become the new root. // ============================================== m_pSrc->SetRootPage(m_pRoot->GetPageId()); dwRes = WriteIdxPage(m_pRoot); m_pSrc->FreePage(dwOldRootId); pParent->Release(); break; } pIdx->Release(); pIdx = pParent; } return dwRes; } //*************************************************************************** // // CBTree::ReplaceBySuccessor // // Removes the wId key in the node, and replaces it with the // successor. // // Precondition: is an internal (non-leaf) node. // // Side-effects: may be overflowed and require the InsertPhase2 // treatment. The node from which the successor is extracted is // written, but may have been reduced to zero keys. // //*************************************************************************** // DWORD CBTree::ReplaceBySuccessor( IN SIdxKeyTable *pIdx, IN WORD wId, OUT LPSTR *pszSuccessorKey, OUT BOOL *pbUnderflowDetected, DWORD Stack[], LONG &StackPtr ) { SIdxKeyTable *pTemp = 0; DWORD dwRes; DWORD dwPredecessorChild; dwRes = FindSuccessorNode(pIdx, wId, &pTemp, &dwPredecessorChild, Stack, StackPtr); if (dwRes || pTemp == 0) return dwRes; LPSTR pszKey = 0; pTemp->GetKeyAt(0, &pszKey); DWORD dwUserData = pTemp->GetUserData(0); pTemp->RemoveKey(0); if (ComputeLoad(pTemp) < const_MinimumLoad) *pbUnderflowDetected = TRUE; dwRes = WriteIdxPage(pTemp); pTemp->Release(); pIdx->RemoveKey(wId); pIdx->AddKey(pszKey, wId, dwUserData); pIdx->SetChildPage(wId, dwPredecessorChild); *pszSuccessorKey = pszKey; StackPtr--; return dwRes; } //*************************************************************************** // // CBTree::FindSuccessorNode // // Read-only. Finds the node containing the successor to the specified key. // //*************************************************************************** // DWORD CBTree::FindSuccessorNode( IN SIdxKeyTable *pIdx, IN WORD wId, OUT SIdxKeyTable **pSuccessor, OUT DWORD *pdwPredecessorChild, DWORD Stack[], LONG &StackPtr ) { SIdxKeyTable *pTemp = 0; DWORD dwRes; DWORD dwSuccessorChild, dwPredecessorChild; dwPredecessorChild = pIdx->GetChildPage(wId); dwSuccessorChild = pIdx->GetChildPage(wId+1); Stack[++StackPtr] = pIdx->GetPageId(); // From this point on, take leftmost children until // we reach a leaf node. The leftmost key in the // leftmost node is always the successor, thanks to the // astonishing properties of the BTree. Nice and easy, huh? // ========================================================= while (dwSuccessorChild) { Stack[++StackPtr] = dwSuccessorChild; if (pTemp) pTemp->Release(); dwRes = ReadIdxPage(dwSuccessorChild, &pTemp); dwSuccessorChild = pTemp->GetChildPage(0); } StackPtr--; // Pop the element we are returning in <*pSuccessor> *pSuccessor = pTemp; if (pdwPredecessorChild) *pdwPredecessorChild = dwPredecessorChild; return dwRes; } //*************************************************************************** // // CBTree::BeginEnum // //*************************************************************************** // DWORD CBTree::BeginEnum( LPSTR pszStartKey, OUT CBTreeIterator **pIterator ) { CBTreeIterator *pIt = new CBTreeIterator; if (pIt == 0) return ERROR_NOT_ENOUGH_MEMORY; DWORD dwRes = pIt->Init(this, pszStartKey); if (dwRes) { pIt->Release(); return dwRes; } *pIterator = pIt; return NO_ERROR; } //*************************************************************************** // // CBTree::Dump // //*************************************************************************** // void CBTree::Dump(FILE *f) { m_pSrc->Dump(f); } //*************************************************************************** // //*************************************************************************** // DWORD CBTree::InvalidateCache() { m_pRoot->Release(); DWORD dwRootPage = m_pSrc->GetRootPage(); DWORD dwRes = ReadIdxPage(dwRootPage, &m_pRoot); return dwRes; } //*************************************************************************** // // CBTreeIterator::Init // //*************************************************************************** // DWORD CBTreeIterator::Init( IN CBTree *pTree, IN LPSTR pszStartKey ) { if (pTree == 0) return ERROR_INVALID_PARAMETER; m_pTree = pTree; // Special case of enumerating everything. Probably not useful // for WMI, but great for testing & debugging (I think). // ============================================================ if (pszStartKey == 0) { Push(0, 0); // Sentinel value in stack SIdxKeyTable *pRoot = pTree->m_pRoot; pRoot->AddRef(); Push(pRoot, 0); DWORD dwChildPage = Peek()->GetChildPage(0); while (dwChildPage) { SIdxKeyTable *pIdx = 0; DWORD dwRes = m_pTree->ReadIdxPage(dwChildPage, &pIdx); if (dwRes) return dwRes; if (StackFull()) { pIdx->Release(); return ERROR_INSUFFICIENT_BUFFER; } Push(pIdx, 0); dwChildPage = pIdx->GetChildPage(0); } return NO_ERROR; } // If here, a matching string was specified. // This is the typical case. // ========================================= Push(0, 0); // Sentinel value in stack WORD wId; DWORD dwRes, dwChildPage; SIdxKeyTable *pIdx = pTree->m_pRoot; pIdx->AddRef(); while (1) { dwRes = pIdx->FindKey(pszStartKey, &wId); if (dwRes == 0) { // Found it Push(pIdx, wId); return NO_ERROR; } // Otherwise, we have to try to descend to a child page. // ===================================================== dwChildPage = pIdx->GetChildPage(wId); if (dwChildPage == 0) break; Push(pIdx, wId); pIdx = 0; dwRes = pTree->ReadIdxPage(dwChildPage, &pIdx); if (dwRes) return dwRes; } Push(pIdx, wId); while (Peek() && PeekId() == WORD(Peek()->GetNumKeys())) Pop(); return NO_ERROR; } //*************************************************************************** // // CBTreeIterator::Next // // On entry: // is the key to visit in the current node (top-of-stack). // The call sets up the successor before leaving. If there is no successor, // the top of stack is left at NULL and ERROR_NO_MORE_ITEMS is returned. // // Returns ERROR_NO_MORE_ITEMS when the iteration is complete. // //*************************************************************************** // DWORD CBTreeIterator::Next( LPSTR *ppszStr, DWORD *pdwData ) { DWORD dwRes; if (ppszStr == 0) return ERROR_INVALID_PARAMETER; *ppszStr = 0; if (Peek() == 0) return ERROR_NO_MORE_ITEMS; // Get the item for the caller. // ============================ dwRes = Peek()->GetKeyAt(PeekId(), ppszStr); if (dwRes) return dwRes; if (pdwData) *pdwData = Peek()->GetUserData(PeekId()); IncStackId(); // Now find the successor. // ======================= DWORD dwChildPage = Peek()->GetChildPage(PeekId()); while (dwChildPage) { SIdxKeyTable *pIdx = 0; dwRes = m_pTree->ReadIdxPage(dwChildPage, &pIdx); if (dwRes) return dwRes; if (StackFull()) { pIdx->Release(); return ERROR_INSUFFICIENT_BUFFER; } Push(pIdx, 0); dwChildPage = pIdx->GetChildPage(0); } // If here, we are at a leaf node. // =============================== while (Peek() && PeekId() == WORD(Peek()->GetNumKeys())) Pop(); return NO_ERROR; } //*************************************************************************** // // CBTreeIterator::Release // //*************************************************************************** // DWORD CBTreeIterator::Release() { delete this; return 0; } //*************************************************************************** // // CBTreeIterator::~CBTreeIterator // //*************************************************************************** // CBTreeIterator::~CBTreeIterator() { // Cleanup any leftover stack while (m_lStackPointer > -1) Pop(); }