496 lines
13 KiB
C
496 lines
13 KiB
C
/*****************************************************************************
|
|
* *
|
|
* BTFILL.C *
|
|
* *
|
|
* Copyright (C) Microsoft Corporation 1990 - 1994. *
|
|
* All Rights reserved. *
|
|
* *
|
|
******************************************************************************
|
|
* *
|
|
* Module Intent *
|
|
* *
|
|
* Functions for creating a btree by adding keys in order. This is faster *
|
|
* and the resulting btree is more compact and has adjacent leaf nodes. *
|
|
* *
|
|
******************************************************************************
|
|
* *
|
|
* Current Owner: Binhn *
|
|
* *
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Revision History: Created 08/17/90 by JohnSc
|
|
*
|
|
* 11/12/90 JohnSc RcFillHbt() wasn't setting rcBtreeError to S_OK
|
|
* 11/29/90 RobertBu #ifdef'ed out routines that are not used under
|
|
* windows.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static char s_aszModule[]= __FILE__; /* For error report */
|
|
|
|
|
|
#include <mvopsys.h>
|
|
#include <orkin.h>
|
|
#include <iterror.h>
|
|
#include <misc.h>
|
|
#include <wrapstor.h>
|
|
#include <_mvutil.h>
|
|
|
|
/*************************************************************************
|
|
*
|
|
* INTERNAL PRIVATE FUNCTIONS
|
|
*
|
|
* All of them should be declared near
|
|
*
|
|
*************************************************************************/
|
|
|
|
PRIVATE HRESULT NEAR PASCAL RcGrowCache(QBTHR);
|
|
PRIVATE KEY NEAR PASCAL KeyLeastInSubtree(QBTHR, BK, SHORT, LPVOID);
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT NEAR PASCAL | RcGrowCache |
|
|
* Grow the cache by one level.
|
|
*
|
|
* @parm QBTHR | qbthr |
|
|
* Pointer to B-tree
|
|
*
|
|
* @rdesc rc
|
|
* args OUT: qbthr->bth.cLevels - incremented
|
|
* qbthr->bth.ghCache - locked
|
|
* qbthr->bth.qCache - points to locked ghCache
|
|
*
|
|
* Note: Root is at level 0, leaves at level qbthr->bth.cLevels - 1.
|
|
*
|
|
***************************************************************************/
|
|
PRIVATE HRESULT NEAR PASCAL RcGrowCache(QBTHR qbthr)
|
|
{
|
|
HANDLE gh;
|
|
QB qb;
|
|
SHORT cbcb = CbCacheBlock(qbthr);
|
|
|
|
|
|
qbthr->bth.cLevels++;
|
|
|
|
/* Allocate a new cache block
|
|
*/
|
|
|
|
if ((gh = _GLOBALALLOC(GMEM_SHARE| GMEM_MOVEABLE | GMEM_ZEROINIT,
|
|
(LONG)cbcb * qbthr->bth.cLevels)) == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
qb = (QB)_GLOBALLOCK(gh);
|
|
|
|
/* Copy the old data */
|
|
|
|
QVCOPY(qb + cbcb, qbthr->qCache,
|
|
(LONG)cbcb * (qbthr->bth.cLevels - 1));
|
|
|
|
/* Remove the old cache */
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALFREE(qbthr->ghCache);
|
|
|
|
/* Update pointer to the new block */
|
|
|
|
qbthr->ghCache = gh;
|
|
qbthr->qCache = qb;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func KEY NEAR PASCAL | KeyLeastInSubtree |
|
|
* Return the least key in the subtree speced by bk and icbLevel.
|
|
*
|
|
* @parm QBTHR | qbthr |
|
|
* Pointer to B-tree
|
|
*
|
|
* @parm BK | bk |
|
|
* bk at root of subtree
|
|
*
|
|
* @parm SHORT | icbLevel |
|
|
* level of subtree root
|
|
*
|
|
* @rdesc key - the smallest key in the subtree
|
|
* -1 if error
|
|
*
|
|
* @comm qbthr->ghCache, ->qCache - contents of cache may change
|
|
*
|
|
***************************************************************************/
|
|
|
|
PRIVATE KEY NEAR PASCAL KeyLeastInSubtree(QBTHR qbthr, BK bk,
|
|
SHORT icbLevel, PHRESULT phr)
|
|
{
|
|
QCB qcb;
|
|
SHORT icbMost = qbthr->bth.cLevels - 1;
|
|
|
|
while (icbLevel < icbMost)
|
|
{
|
|
if ((qcb = QFromBk(bk, icbLevel, qbthr, phr)) == NULL)
|
|
return (KEY)-1;
|
|
bk = *(BK UNALIGNED *UNALIGNED)qcb->db.rgbBlock;
|
|
++icbLevel;
|
|
}
|
|
|
|
if ((qcb = QFromBk(bk, icbLevel, qbthr, phr)) == NULL)
|
|
return ((KEY)-1);
|
|
return (KEY)qcb->db.rgbBlock + 2 * sizeof(BK);
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc PUBLIC API
|
|
*
|
|
* @func HBT PASCAL FAR | HbtInitFill |
|
|
* Start the btree fill process. Note that the HBT returned
|
|
* is NOT a valid btree handle.
|
|
*
|
|
* @parm LPSTR | sz |
|
|
* btree name
|
|
*
|
|
* @parm BTREE_PARAMS FAR * | qbtp |
|
|
* btree creation parameters
|
|
*
|
|
* @rdesc an HBT that isn't a valid btree handle until RcFiniFillHbt()
|
|
* is called on it (with intervening RcFillHbt()'s)
|
|
* The only valid operations on this HBT are
|
|
* RcFillHbt() - add keys in order one at a time
|
|
* RcAbandonHbt() - junk the hbt
|
|
* RcFiniFillHbt() - finish adding keys. After this, the
|
|
* hbt is a normal btree handle.
|
|
*
|
|
* @comm Method:
|
|
* Create a btree. Create a single-block cache.
|
|
*
|
|
***************************************************************************/
|
|
PUBLIC HBT PASCAL FAR EXPORT_API HbtInitFill(LPSTR sz,
|
|
BTREE_PARAMS FAR *qbtp, PHRESULT phr)
|
|
{
|
|
HBT hbt;
|
|
QBTHR qbthr;
|
|
QCB qcb;
|
|
|
|
|
|
// Get a btree handle
|
|
|
|
if ((hbt = HbtCreateBtreeSz(sz, qbtp, phr)) == 0)
|
|
return 0;
|
|
|
|
qbthr = (QBTHR)_GLOBALLOCK(hbt);
|
|
|
|
|
|
// make a one-block cache
|
|
|
|
if ((qbthr->ghCache = _GLOBALALLOC(GMEM_ZEROINIT|GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
{
|
|
SetErrCode (phr, E_OUTOFMEMORY);
|
|
_GLOBALUNLOCK(hbt);
|
|
RcAbandonHbt(hbt);
|
|
return 0;
|
|
}
|
|
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
qcb = (QCB)qbthr->qCache;
|
|
|
|
qbthr->bth.cLevels = 1;
|
|
qbthr->bth.bkFirst = qbthr->bth.bkLast = qcb->bk = BkAlloc(qbthr, phr);
|
|
qcb->bFlags = fCacheDirty | fCacheValid;
|
|
qcb->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- 2 * sizeof(BK);
|
|
qcb->db.cKeys = 0;
|
|
|
|
SetBkPrev(qcb, bkNil);
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
return hbt;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc PUBLIC API
|
|
*
|
|
* @func HRESULT PASCAL FAR | RcFillHbt |
|
|
* Add a key and record (in order) to the "HBT" given.
|
|
*
|
|
* @parm HBT | hbt |
|
|
* NOT a valid hbt: it was produced with HbtInitFill().
|
|
*
|
|
* @parm KEY | key |
|
|
* key to add. Must be greater than all keys previously added.
|
|
*
|
|
* qvRec- record associated with key
|
|
*
|
|
* PROMISES
|
|
* returns: error code
|
|
* args OUT: hbt - key, record added
|
|
* +++
|
|
*
|
|
* Method: If key and record don't fit in current leaf, allocate a
|
|
* new one and make it the current one.
|
|
* Add key and record to current block.
|
|
*
|
|
***************************************************************************/
|
|
HRESULT PASCAL FAR EXPORT_API RcFillHbt(HBT hbt, KEY key, QV qvRec)
|
|
{
|
|
QBTHR qbthr;
|
|
QCB qcb;
|
|
SHORT cbRec, cbKey;
|
|
QB qb;
|
|
HRESULT rc;
|
|
|
|
|
|
/* Sanity check */
|
|
if (hbt == 0 || key == 0 || qvRec == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
qbthr = (QBTHR)_GLOBALLOCK(hbt);
|
|
qcb = (QCB)_GLOBALLOCK(qbthr->ghCache);
|
|
|
|
cbRec = CbSizeRec(qvRec, qbthr);
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
|
|
// Make sure key and record aren't too big for even an empty block.
|
|
if (cbRec + cbKey > (qbthr->bth.cbBlock / 2))
|
|
{
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (cbRec + cbKey > qcb->db.cbSlack) {
|
|
|
|
// key and rec don't fit in this block: write it out
|
|
SetBkNext(qcb, BkAlloc(qbthr, NULL));
|
|
if ((rc = RcWriteBlock(qcb, qbthr)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALFREE(qbthr->ghCache);
|
|
RcAbandonHf(qbthr->hf);
|
|
_GLOBALUNLOCK(hbt);
|
|
_GLOBALFREE(hbt);
|
|
return rc;
|
|
}
|
|
|
|
// recycle the block
|
|
|
|
SetBkPrev(qcb, qcb->bk);
|
|
qcb->bk = BkNext(qcb);
|
|
qcb->bFlags = fCacheDirty | fCacheValid;
|
|
qcb->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- 2 * sizeof(BK);
|
|
qcb->db.cKeys = 0;
|
|
|
|
}
|
|
|
|
// add key and rec to the current block;
|
|
|
|
qb = (QB)&(qcb->db) + qbthr->bth.cbBlock - qcb->db.cbSlack;
|
|
QVCOPY(qb, (QV)key, (LONG)cbKey);
|
|
QVCOPY(qb + cbKey, qvRec, (LONG)cbRec);
|
|
qcb->db.cKeys++;
|
|
qcb->db.cbSlack -= (cbKey + cbRec);
|
|
qbthr->bth.lcEntries++;
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc PUBLIC API
|
|
*
|
|
* @func HRESULT PASCAL FAR | RcFiniFillHbt |
|
|
* Complete filling of the hbt. After this call, the hbt is a valid
|
|
* btree handle.
|
|
*
|
|
* @parm HBT | hbt |
|
|
* NOT a valid hbt: created with RcInitFillHbt()
|
|
* and filled with keys & records by RcFillHbt().
|
|
*
|
|
* @rdesc error code
|
|
* hbt - a valid hbt (on S_OK)
|
|
*
|
|
* @comm Take the first key of each leaf block, creating a layer
|
|
* of internal nodes.
|
|
* Take the first key in each node in this layer to create
|
|
* another layer of internal nodes. Repeat until we get
|
|
* we get a layer with only one node. That's the root.
|
|
*
|
|
***************************************************************************/
|
|
PUBLIC HRESULT PASCAL FAR EXPORT_API RcFiniFillHbt(HBT hbt)
|
|
{
|
|
BK bkThisMin, bkThisMost, bkThisCur, // level being scanned
|
|
bkTopMin, bkTopMost; // level being created
|
|
QBTHR qbthr;
|
|
QCB qcbThis, qcbTop;
|
|
SHORT cbKey;
|
|
KEY key;
|
|
QB qbDst;
|
|
HRESULT rc = S_OK;
|
|
|
|
|
|
if ((qbthr = (QBTHR)_GLOBALLOCK(hbt)) == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache); // we know cache is valid
|
|
|
|
qcbThis = QCacheBlock(qbthr, 0);
|
|
|
|
SetBkNext(qcbThis, bkNil);
|
|
|
|
bkThisMin = qbthr->bth.bkFirst;
|
|
bkThisMost = qbthr->bth.bkLast = qcbThis->bk;
|
|
|
|
|
|
if (bkThisMin == bkThisMost)
|
|
{ // only one leaf
|
|
qbthr->bth.bkRoot = bkThisMin;
|
|
goto normal_return;
|
|
}
|
|
|
|
if ((rc = RcGrowCache( qbthr)) != S_OK)
|
|
{
|
|
goto error_return;
|
|
}
|
|
|
|
qcbTop = QCacheBlock(qbthr, 0);
|
|
qcbTop->bk = bkTopMin = bkTopMost = BkAlloc(qbthr, NULL);
|
|
qcbTop->bFlags = fCacheDirty | fCacheValid;
|
|
qcbTop->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- sizeof(BK);
|
|
qcbTop->db.cKeys = 0;
|
|
|
|
// Get first key from each leaf node and build a layer of internal nodes.
|
|
|
|
// add bk of first leaf to the node
|
|
|
|
qbDst = qcbTop->db.rgbBlock;
|
|
*(BK UNALIGNED *UNALIGNED)qbDst = bkThisMin;
|
|
qbDst += sizeof(BK);
|
|
|
|
for (bkThisCur = bkThisMin + 1; bkThisCur <= bkThisMost; ++bkThisCur)
|
|
{
|
|
qcbThis = QFromBk(bkThisCur, 1, qbthr, NULL);
|
|
|
|
key = (KEY)(qcbThis->db.rgbBlock + 2 * sizeof( BK));
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
|
|
if ((SHORT)(cbKey + sizeof( BK)) > qcbTop->db.cbSlack)
|
|
{
|
|
// key and bk don't fit in this block: write it out
|
|
rc = RcWriteBlock(qcbTop, qbthr);
|
|
|
|
// recycle the block
|
|
qcbTop->bk = bkTopMost = BkAlloc(qbthr, NULL);
|
|
qcbTop->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- sizeof(BK); // (bk added below)
|
|
qcbTop->db.cKeys = 0;
|
|
qbDst = qcbTop->db.rgbBlock;
|
|
}
|
|
else {
|
|
qcbTop->db.cbSlack -= cbKey + sizeof(BK);
|
|
QVCOPY(qbDst, (QB)key, cbKey);
|
|
qbDst += cbKey;
|
|
qcbTop->db.cKeys++;
|
|
}
|
|
|
|
*(BK UNALIGNED *UNALIGNED)qbDst = bkThisCur;
|
|
qbDst += sizeof(BK);
|
|
}
|
|
|
|
|
|
// Keep adding layers of internal nodes until we have a root.
|
|
|
|
while (bkTopMost > bkTopMin)
|
|
{
|
|
bkThisMin = bkTopMin;
|
|
bkThisMost = bkTopMost;
|
|
bkTopMin = bkTopMost = BkAlloc(qbthr, NULL);
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
rc = RcGrowCache(qbthr);
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
if (rc != S_OK)
|
|
{
|
|
goto error_return;
|
|
}
|
|
|
|
qcbTop = QCacheBlock(qbthr, 0);
|
|
qcbTop->bk = bkTopMin;
|
|
qcbTop->bFlags = fCacheDirty | fCacheValid;
|
|
qcbTop->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- sizeof(BK);
|
|
qcbTop->db.cKeys = 0;
|
|
|
|
|
|
// add bk of first node of this level to current node of top level;
|
|
qbDst = qcbTop->db.rgbBlock;
|
|
*(BK UNALIGNED *UNALIGNED)qbDst = bkThisMin;
|
|
qbDst += sizeof(BK);
|
|
|
|
|
|
// for (each internal node in this level after first)
|
|
|
|
for (bkThisCur = bkThisMin + 1;
|
|
bkThisCur <= bkThisMost; ++bkThisCur) {
|
|
key = KeyLeastInSubtree(qbthr, bkThisCur, 1, NULL);
|
|
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
|
|
if ((SHORT)(cbKey + sizeof( BK)) > qcbTop->db.cbSlack) {
|
|
|
|
// key and bk don't fit in this block: write it out
|
|
|
|
rc = RcWriteBlock(qcbTop, qbthr);
|
|
|
|
// recycle the block
|
|
|
|
qcbTop->bk = bkTopMost = BkAlloc(qbthr, NULL);
|
|
qcbTop->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- sizeof(BK); // (bk added below)
|
|
qcbTop->db.cKeys = 0;
|
|
qbDst = qcbTop->db.rgbBlock;
|
|
}
|
|
else {
|
|
qcbTop->db.cbSlack -= cbKey + sizeof(BK);
|
|
QVCOPY(qbDst, (QB)key, cbKey);
|
|
qbDst += cbKey;
|
|
qcbTop->db.cKeys++;
|
|
}
|
|
|
|
*(BK UNALIGNED *UNALIGNED)qbDst = bkThisCur;
|
|
qbDst += sizeof(BK);
|
|
}
|
|
}
|
|
|
|
assert(bkTopMin == bkTopMost);
|
|
|
|
qbthr->bth.bkRoot = bkTopMin;
|
|
qbthr->bth.bkEOF = bkTopMin + 1;
|
|
|
|
normal_return:
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
return rc;
|
|
|
|
error_return:
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
RcAbandonHbt(hbt);
|
|
return (rc);
|
|
}
|
|
|
|
/* EOF */
|