1061 lines
26 KiB
C
1061 lines
26 KiB
C
/*****************************************************************************
|
|
* *
|
|
* BTINSERT.C *
|
|
* *
|
|
* Copyright (C) Microsoft Corporation 1989 - 1994. *
|
|
* All Rights reserved. *
|
|
* *
|
|
******************************************************************************
|
|
* *
|
|
* Module Intent *
|
|
* *
|
|
* Btree insertion functions and helpers. *
|
|
* *
|
|
******************************************************************************
|
|
* *
|
|
* Current Owner: BinhN *
|
|
* *
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Revision History: Created 04/20/89 by JohnSc
|
|
*
|
|
* 08/21/90 JohnSc autodocified
|
|
* 04-Feb-1991 JohnSc set ghCache to NULL after freeing it
|
|
* 3/05/97 erinfox Change errors to HRESULTS
|
|
*****************************************************************************/
|
|
|
|
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 GLOBAL FUNCTIONS
|
|
*
|
|
* All of them should be declared far, unless they are known to be called
|
|
* in the same segment. They should be prototyped in some include file
|
|
*
|
|
*************************************************************************/
|
|
|
|
PUBLIC BK PASCAL FAR BkAlloc(QBTHR, LPVOID);
|
|
PUBLIC HRESULT PASCAL FAR RcInsertInternal(BK, KEY, SHORT, QBTHR);
|
|
PUBLIC HRESULT PASCAL FAR RcSplitLeaf(QCB, QCB, QBTHR);
|
|
PUBLIC void PASCAL FAR SplitInternal(QCB, QCB, QBTHR, QW);
|
|
|
|
/*************************************************************************
|
|
*
|
|
* API FUNCTIONS
|
|
* Those functions should be exported in a .DEF file
|
|
*************************************************************************/
|
|
|
|
PUBLIC HRESULT PASCAL FAR EXPORT_API RcUpdateHbt(HBT, KEY, QV);
|
|
PUBLIC HRESULT PASCAL FAR EXPORT_API RcInsertHbt(HBT , KEY, QV);
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func BK PASCAL FAR | BkAlloc |
|
|
* Make up a new BK.
|
|
*
|
|
* @parm QBTHR | qbthr |
|
|
* Pointer to B-tree strucuture.
|
|
* qbthr->bkFree - head of free list, unless it's bkNil.
|
|
* qbthr->bkEOF - use this if bkFree == bkNil (then ++)
|
|
*
|
|
* @rdesc a valid BK or bkNil if file is hosed
|
|
* args OUT: qbthr->bkFree or qbthr->bkEOF will be different
|
|
*
|
|
* @comm Side Effects: btree file may grow
|
|
* Method: Use the head of the free list. If the free list is empty,
|
|
* there are no holes in the file and we carve a new one.
|
|
*
|
|
***************************************************************************/
|
|
|
|
PUBLIC BK PASCAL FAR BkAlloc(QBTHR qbthr, PHRESULT phr)
|
|
{
|
|
BK bk;
|
|
|
|
if (qbthr->bth.bkFree == bkNil)
|
|
bk = (qbthr->bth.bkEOF++);
|
|
else
|
|
{
|
|
FILEOFFSET foSeek;
|
|
bk = qbthr->bth.bkFree;
|
|
foSeek=FoFromBk(bk,qbthr);
|
|
|
|
if (!FoEquals(FoSeekHf(qbthr->hf, foSeek, wFSSeekSet, phr),foSeek ))
|
|
return bkNil;
|
|
|
|
if (LcbReadHf(qbthr->hf, &(qbthr->bth.bkFree), (LONG)sizeof(BK),
|
|
phr) != (LONG)sizeof(BK))
|
|
return bkNil;
|
|
}
|
|
|
|
return bk;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT PASCAL FAR | RcSplitLeaf |
|
|
* Split a leaf node when a new key won't fit into it.
|
|
*
|
|
* @parm QCB | qcbOld |
|
|
* the leaf to be split
|
|
*
|
|
* @parm QCB | qcbNew |
|
|
* a leaf buffer to get half the contents of qcbOld;
|
|
* qcbNew->bk must be set
|
|
*
|
|
* @parm QBTHR | qbthr |
|
|
* Pointer to B-tree structure
|
|
*
|
|
* @rdesc S_OK, E_OUTOFMEMORY
|
|
* args OUT: qcbOld - cbSlack, cKeys, bkPrev, bkNext updated
|
|
* qcbNew - about half of the old contents of qcbOld
|
|
* get put here. cbSlack, cKeys set.
|
|
* qbthr - qbthr->bkFirst and bkLast can be changed
|
|
* globals OUT: rcBtreeError
|
|
*
|
|
* @comm ompressed keys not implemented
|
|
* For fixed length keys and records, could just split at
|
|
* middle key rather than scanning from the beginning.
|
|
*
|
|
* The new block is always after the old block. This is
|
|
* why we don't have to adjust pointers to the old block
|
|
* (i.e. qbthr->bth.bkFirst).
|
|
*
|
|
***************************************************************************/
|
|
|
|
PUBLIC HRESULT PASCAL FAR RcSplitLeaf(QCB qcbOld, QCB qcbNew, QBTHR qbthr)
|
|
{
|
|
SHORT iOK, iNext, iHalf, cbKey, cbRec, cKeys;
|
|
QB q;
|
|
HANDLE gh;
|
|
QCB qcb;
|
|
HRESULT rc;
|
|
SHORT cbCopyToNew;
|
|
|
|
assert(qcbOld->bFlags & fCacheValid);
|
|
|
|
iOK = iNext = 0;
|
|
q = qcbOld->db.rgbBlock + 2 * sizeof(BK);
|
|
iHalf = (qbthr->bth.cbBlock / 2) - sizeof(BK);
|
|
|
|
for (cKeys = qcbOld->db.cKeys; ;)
|
|
{
|
|
assert(cKeys > 0);
|
|
|
|
cbKey = CbSizeKey((KEY)q, qbthr, TRUE);
|
|
cbRec = CbSizeRec(q + cbKey, qbthr);
|
|
|
|
iNext = iOK + cbKey + cbRec;
|
|
|
|
if (iNext > iHalf) break;
|
|
|
|
q += cbKey + cbRec;
|
|
iOK = iNext;
|
|
cKeys--;
|
|
}
|
|
|
|
// >>>> if compressed, expand first key here
|
|
|
|
// Note that the total block size includes the disk block struct.
|
|
// The new slack in the old block should equal the number of bytes
|
|
// copied to the new block. The amount being copied was previously too large
|
|
// by 4 bytes.
|
|
cbCopyToNew = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1 - (iOK + 2 * sizeof(BK));
|
|
|
|
QVCOPY(qcbNew->db.rgbBlock + 2 * sizeof(BK),
|
|
qcbOld->db.rgbBlock + 2 * sizeof(BK) + iOK, (LONG)cbCopyToNew);
|
|
|
|
qcbNew->db.cKeys = cKeys;
|
|
qcbOld->db.cKeys -= cKeys;
|
|
|
|
qcbNew->db.cbSlack = qcbOld->db.cbSlack + iOK;
|
|
qcbOld->db.cbSlack = cbCopyToNew;
|
|
|
|
|
|
qcbOld->bFlags |= fCacheDirty | fCacheValid;
|
|
qcbNew->bFlags = fCacheDirty | fCacheValid;
|
|
|
|
SetBkPrev(qcbNew, qcbOld->bk);
|
|
SetBkNext(qcbNew, BkNext(qcbOld));
|
|
SetBkNext(qcbOld, qcbNew->bk);
|
|
|
|
if (BkNext(qcbNew) == bkNil)
|
|
qbthr->bth.bkLast = qcbNew->bk;
|
|
else
|
|
{
|
|
|
|
/* set new->next->prev = new; */
|
|
|
|
if ((gh = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
return (E_OUTOFMEMORY);
|
|
qcb = _GLOBALLOCK(gh);
|
|
|
|
qcb->bk = BkNext(qcbNew);
|
|
|
|
if ((rc = FReadBlock(qcb, qbthr)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
return rc;
|
|
}
|
|
|
|
SetBkPrev(qcb, qcbNew->bk);
|
|
if ((rc = RcWriteBlock(qcb, qbthr)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
return rc;
|
|
}
|
|
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
}
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func void PASCAL FAR | SplitInternal |
|
|
*
|
|
* Split an internal node node when a new key won't fit into it.
|
|
* Old node gets BKs and KEYs up to the first key that won't
|
|
* fit in half the block size. (Leave that key there with iKey
|
|
* pointing at it). The new block gets the BKs and KEYs after
|
|
* that key.
|
|
*
|
|
* @parm QCB | qcbOld |
|
|
* the block to split
|
|
*
|
|
* @parm QCB | qcbNew |
|
|
* pointer to a qcb
|
|
*
|
|
* @parm QBTHR | qbthr |
|
|
* Pointer to B-tree structure
|
|
*
|
|
* @rdesc qcbNew - keys and records copied to this buffer.
|
|
* cbSlack, cKeys set.
|
|
* qcbOld - cbSlack and cKeys updated.
|
|
* qi - index into qcbOld->db.rgbBlock of discriminating key
|
|
*
|
|
* @comm compressed keys not implemented
|
|
* *qi is index of a key that is not valid for qcbOld. This
|
|
* key gets copied into the parent node.
|
|
*
|
|
***************************************************************************/
|
|
|
|
PUBLIC void PASCAL FAR SplitInternal(QCB qcbOld, QCB qcbNew, QBTHR qbthr, QW qi)
|
|
{
|
|
SHORT iOK, iNext, iHalf, cb, cKeys, cbTotal;
|
|
QB q;
|
|
|
|
assert(qcbOld->bFlags & fCacheValid);
|
|
|
|
iOK = iNext = sizeof(BK);
|
|
q = qcbOld->db.rgbBlock + sizeof(BK);
|
|
iHalf = qbthr->bth.cbBlock / 2;
|
|
|
|
for (cKeys = qcbOld->db.cKeys; ; cKeys--)
|
|
{
|
|
assert(cKeys > 0);
|
|
|
|
cb = CbSizeKey((KEY)q, qbthr, TRUE) + sizeof(BK);
|
|
iNext = iOK + cb;
|
|
|
|
if (iNext > iHalf) break;
|
|
|
|
q += cb;
|
|
iOK = iNext;
|
|
}
|
|
|
|
// have to expand first key if compressed
|
|
|
|
cbTotal = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1;
|
|
|
|
QVCOPY(qcbNew->db.rgbBlock,
|
|
qcbOld->db.rgbBlock + iNext - sizeof(BK),
|
|
(LONG)cbTotal - qcbOld->db.cbSlack - iNext + sizeof(BK));
|
|
|
|
*qi = iOK;
|
|
|
|
qcbNew->db.cKeys = cKeys - 1;
|
|
qcbOld->db.cKeys -= cKeys;
|
|
|
|
qcbNew->db.cbSlack = qcbOld->db.cbSlack + iNext - sizeof(BK);
|
|
qcbOld->db.cbSlack = cbTotal - iOK;
|
|
|
|
qcbOld->bFlags |= fCacheDirty | fCacheValid;
|
|
qcbNew->bFlags = fCacheDirty | fCacheValid;
|
|
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT PASCAL FAR | RcInsertInternal |
|
|
* Insert a bk and key into an internal block.
|
|
* state IN: We've just done a lookup, so all ancestors are cached.
|
|
* Cache is locked.
|
|
*
|
|
*
|
|
* @parm BK | bk | BK to insert
|
|
*
|
|
* @parm KEY | key | least key in bk
|
|
*
|
|
* @parm SHORT | wLevel |
|
|
* level of the block we're inserting
|
|
*
|
|
* @parm QNTHR | qbthr |
|
|
* btree header
|
|
*
|
|
*
|
|
* @rdesc S_OK, E_OUTOFMEMORY
|
|
* args OUT: qbthr->cLevels - incremented if root is split
|
|
* qbthr->ghCache, qbthr->qCache - may change if root is
|
|
* split and cache therefore grows
|
|
* state OUT: Cache locked, all ancestors cached.
|
|
*
|
|
* @comm
|
|
* Status: compressed keys unimplemented
|
|
* Method: Works recursively. Splits root if need be.
|
|
* Side Effects: Cache could be different after this call than it
|
|
* was before.
|
|
* Pointers or handles to it from before this call could be
|
|
* invalid. Use qbthr->ghCache or qbthr->qCache to be safe.
|
|
*
|
|
***************************************************************************/
|
|
|
|
PUBLIC HRESULT PASCAL FAR RcInsertInternal(BK bk, KEY key, SHORT wLevel, QBTHR qbthr)
|
|
{
|
|
QCB qcb, qcbNew, qcbRoot;
|
|
WORD iKey;
|
|
SHORT cLevels, cbKey, cbCBlock = CbCacheBlock(qbthr);
|
|
QB qb;
|
|
HANDLE gh, ghOldCache;
|
|
KEY keyNew;
|
|
BK bkRoot;
|
|
HRESULT rc = S_OK;
|
|
UINT_PTR iKeySav = 0;
|
|
ERRB errb;
|
|
|
|
|
|
cbKey = CbSizeKey(key, qbthr, TRUE);
|
|
|
|
if (wLevel == 0)
|
|
{
|
|
|
|
/* inserting another block at root level */
|
|
// allocate new root bk;
|
|
|
|
bkRoot = BkAlloc(qbthr, &errb);
|
|
if (bkRoot == bkNil)
|
|
{
|
|
return errb;
|
|
}
|
|
|
|
// grow cache by one cache block;
|
|
qbthr->bth.cLevels++;
|
|
|
|
gh = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)cbCBlock * qbthr->bth.cLevels);
|
|
if (gh == NULL)
|
|
return (E_OUTOFMEMORY);
|
|
qb = _GLOBALLOCK(gh);
|
|
|
|
QVCOPY(qb + cbCBlock, qbthr->qCache,
|
|
(LONG)cbCBlock * (qbthr->bth.cLevels - 1));
|
|
|
|
/* Since key points into the cache if this is a recursive */
|
|
/* call, we can't free the old cache until a bit later. */
|
|
|
|
ghOldCache = qbthr->ghCache;
|
|
qbthr->ghCache = gh;
|
|
qbthr->qCache = qb;
|
|
|
|
// put old root bk, key, bk into new root block;
|
|
|
|
qcbRoot = (QCB)qbthr->qCache;
|
|
|
|
qcbRoot->bk = bkRoot;
|
|
qcbRoot->bFlags = fCacheDirty | fCacheValid;
|
|
qcbRoot->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- (2 * sizeof(BK) + cbKey);
|
|
qcbRoot->db.cKeys = 1;
|
|
|
|
*(BK FAR *)(qcbRoot->db.rgbBlock) = qbthr->bth.bkRoot;
|
|
|
|
QVCOPY(qcbRoot->db.rgbBlock + sizeof(BK), (QB)key, (LONG)cbKey);
|
|
|
|
/* OK, now we're done with key, so we can safely free the */
|
|
/* old cache. */
|
|
_GLOBALUNLOCK(ghOldCache);
|
|
_GLOBALFREE(ghOldCache);
|
|
|
|
*(BK FAR *)(qcbRoot->db.rgbBlock + sizeof(BK) + cbKey) = bk;
|
|
|
|
qbthr->bth.bkRoot = bkRoot;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
qcb = QCacheBlock(qbthr, wLevel - 1);
|
|
|
|
if ((SHORT)(cbKey + sizeof(BK)) >= qcb->db.cbSlack) {
|
|
|
|
// new key and BK won't fit in block
|
|
// split the block;
|
|
|
|
if ((gh = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
return (E_OUTOFMEMORY);
|
|
|
|
qcbNew = _GLOBALLOCK(gh);
|
|
if ((qcbNew->bk = BkAlloc(qbthr, &errb)) == bkNil) {
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
return errb;
|
|
}
|
|
|
|
SplitInternal(qcb, qcbNew, qbthr, &iKey);
|
|
keyNew = (KEY)qcb->db.rgbBlock + iKey;
|
|
|
|
cLevels = qbthr->bth.cLevels;
|
|
|
|
if (wLevel < cLevels - 1)
|
|
{
|
|
/* This is a recursive call (the arg bk doesn't refer to a leaf.)
|
|
** This means that the arg key points into the cache, so it will
|
|
** be invalid if the root is split.
|
|
** Verify with some asserts that key points into the cache.
|
|
*/
|
|
assert((QB)key > qbthr->qCache + CbCacheBlock(qbthr));
|
|
assert((QB)key < qbthr->qCache + (wLevel + 1) * CbCacheBlock(qbthr));
|
|
|
|
/* Save the offset of key into the cache block. Recall that key
|
|
** is the first invalid key in an internal node that has just
|
|
** been split. It points into the part that is still in the cache.
|
|
*/
|
|
iKeySav = (QB)key - (qbthr->qCache + wLevel * CbCacheBlock(qbthr));
|
|
}
|
|
|
|
if ((rc = RcInsertInternal(qcbNew->bk, (KEY)qcb->db.rgbBlock + iKey,
|
|
(SHORT)(wLevel - 1), qbthr)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
return rc;
|
|
}
|
|
|
|
/* RcInsertInternal() can change cache and qbthr->bth.cLevels */
|
|
if (cLevels != qbthr->bth.cLevels)
|
|
{
|
|
assert(cLevels + 1 == qbthr->bth.cLevels);
|
|
wLevel++;
|
|
qcb = QCacheBlock(qbthr, wLevel - 1);
|
|
keyNew = (KEY)qcb->db.rgbBlock + iKey;
|
|
|
|
/* Also restore the arg "key" if it pointed into the cache.
|
|
*/
|
|
if (iKeySav)
|
|
{
|
|
key = (KEY)(qbthr->qCache + wLevel * CbCacheBlock(qbthr)
|
|
+ iKeySav);
|
|
}
|
|
}
|
|
|
|
/* find out which block to put new key and bk in, and cache it */
|
|
if (WCmpKey(key, keyNew, qbthr) < 0)
|
|
{
|
|
if ((rc = RcWriteBlock(qcbNew, qbthr)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
return rc;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// write old block and cache the new one
|
|
if ((rc = RcWriteBlock(qcb, qbthr)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
return rc;
|
|
}
|
|
QVCOPY(qcb, qcbNew, (LONG)CbCacheBlock(qbthr));
|
|
}
|
|
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
|
|
}
|
|
|
|
// slide stuff over and insert the new key, bk
|
|
|
|
/* get pos */
|
|
if (qbthr->BkScanInternal(qcb->bk, key, (SHORT)(wLevel - 1), qbthr,
|
|
&iKey, &errb) == bkNil)
|
|
{
|
|
return errb;
|
|
}
|
|
|
|
assert(iKey + cbKey + sizeof(BK) <
|
|
qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1);
|
|
|
|
qb = (QB)(qcb->db.rgbBlock) + iKey;
|
|
|
|
QVCOPY(qb + cbKey + sizeof(BK), qb,
|
|
(LONG)qbthr->bth.cbBlock - iKey - qcb->db.cbSlack
|
|
- sizeof(DISK_BLOCK) + 1);
|
|
|
|
QVCOPY(qb, (QB)key, (LONG)cbKey);
|
|
*(BK FAR *)(qb + cbKey) = bk;
|
|
|
|
qcb->db.cKeys++;
|
|
qcb->db.cbSlack -= (cbKey + sizeof(BK));
|
|
qcb->bFlags |= fCacheDirty;
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc PUBLIC API
|
|
*
|
|
* @func HRESULT PASCAL FAR | RcInsertHbt |
|
|
* Insert a key and record into a btree
|
|
*
|
|
* @parm HBT | hbt |
|
|
* btree handle
|
|
*
|
|
* @parm KEY | key |
|
|
* key to insert
|
|
*
|
|
* @parm QV | qvRec |
|
|
* record associated with key to insert
|
|
*
|
|
* @rdesc S_OK, E_DUPLICATE (duplicate key)
|
|
*
|
|
* @comm
|
|
* state IN: cache unlocked
|
|
* state OUT: cache unlocked, all ancestor blocks cached
|
|
* Notes: compressed keys unimplemented
|
|
*
|
|
***************************************************************************/
|
|
|
|
PUBLIC HRESULT PASCAL FAR EXPORT_API RcInsertHbt(HBT hbt, KEY key, QV qvRec)
|
|
{
|
|
QBTHR qbthr;
|
|
HF hf;
|
|
HRESULT rc;
|
|
SHORT cbAdd, cbKey, cbRec;
|
|
QCB qcbLeaf, qcbNew, qcb;
|
|
HANDLE gh;
|
|
KEY keyNew;
|
|
QB qb;
|
|
BTPOS btpos;
|
|
ERRB errb;
|
|
|
|
|
|
if ((qbthr = _GLOBALLOCK(hbt)) == NULL)
|
|
return(E_INVALIDARG);
|
|
|
|
hf = qbthr->hf;
|
|
|
|
if ((rc = RcLookupByKeyAux(hbt, key, &btpos, NULL, TRUE)) == S_OK)
|
|
{
|
|
rc = E_DUPLICATE;
|
|
exit0:
|
|
_GLOBALUNLOCK (hbt);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
After lookup, all nodes on path from root to correct leaf are
|
|
guaranteed to be cached, with iKey valid.
|
|
*/
|
|
|
|
if (rc != E_NOTEXIST)
|
|
goto exit0;
|
|
|
|
rc = S_OK;
|
|
|
|
if (qbthr->bth.cLevels == 0)
|
|
{
|
|
|
|
// need to build a valid root block
|
|
|
|
if ((qbthr->ghCache = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
{
|
|
rc = E_OUTOFMEMORY;
|
|
goto exit0;
|
|
}
|
|
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
qcb = (QCB)qbthr->qCache;
|
|
|
|
qbthr->bth.cLevels = 1;
|
|
qbthr->bth.bkFirst = qbthr->bth.bkLast = qbthr->bth.bkRoot =
|
|
qcb->bk = BkAlloc(qbthr, &errb);
|
|
|
|
if (qcb->bk == bkNil)
|
|
{
|
|
exit01:
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALFREE(qbthr->ghCache);
|
|
qbthr->ghCache = NULL;
|
|
rc = errb;
|
|
goto exit0;
|
|
}
|
|
qcb->bFlags = fCacheDirty | fCacheValid;
|
|
qcb->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- 2 * sizeof(BK);
|
|
qcb->db.cKeys = 0;
|
|
SetBkPrev(qcb, bkNil);
|
|
SetBkNext(qcb, bkNil);
|
|
btpos.iKey = 2 * sizeof(BK);
|
|
|
|
}
|
|
else
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
cbRec = CbSizeRec(qvRec, qbthr);
|
|
cbAdd = cbKey + cbRec;
|
|
|
|
/* check to see if key and rec can fit harmoniously in a block */
|
|
|
|
if (cbAdd > qbthr->bth.cbBlock / 2)
|
|
{
|
|
rc = E_FAIL;
|
|
goto exit01;
|
|
}
|
|
|
|
qcbLeaf = QCacheBlock(qbthr, qbthr->bth.cLevels - 1);
|
|
|
|
if (cbAdd > qcbLeaf->db.cbSlack)
|
|
{
|
|
/* new key and rec don't fit in leaf: split the block */
|
|
|
|
/* create new leaf block */
|
|
|
|
if ((gh = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
{
|
|
rc = E_OUTOFMEMORY;
|
|
goto exit01;
|
|
}
|
|
qcbNew = _GLOBALLOCK(gh);
|
|
|
|
if ((qcbNew->bk = BkAlloc(qbthr, &errb)) == bkNil)
|
|
{
|
|
rc = errb;
|
|
exit02:
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
goto exit01;
|
|
}
|
|
|
|
if ((rc = RcSplitLeaf(qcbLeaf, qcbNew, qbthr)) != S_OK)
|
|
goto exit02;
|
|
|
|
keyNew = (KEY)qcbNew->db.rgbBlock + 2 * sizeof(BK);
|
|
|
|
/* insert new leaf into parent block */
|
|
|
|
if ((rc = RcInsertInternal(qcbNew->bk,
|
|
keyNew, (SHORT)(qbthr->bth.cLevels - 1), qbthr)) != S_OK)
|
|
{
|
|
goto exit02;
|
|
}
|
|
|
|
// InsertInternal can invalidate cache block pointers..
|
|
|
|
qcbLeaf = QCacheBlock(qbthr, qbthr->bth.cLevels - 1);
|
|
|
|
/* find out which leaf to put new key and rec in and cache it */
|
|
|
|
if (WCmpKey(key, keyNew, qbthr) >= 0)
|
|
{
|
|
/* key goes in new block. Write out old one and cache the new one */
|
|
if ((rc = RcWriteBlock(qcbLeaf, qbthr)) != S_OK)
|
|
goto exit02;
|
|
|
|
QVCOPY(qcbLeaf, qcbNew, (LONG)CbCacheBlock(qbthr));
|
|
|
|
/* get pos */
|
|
if ((rc = qbthr->RcScanLeaf(qcbLeaf->bk, key,
|
|
(SHORT)(qbthr->bth.cLevels - 1),
|
|
qbthr, NULL, &btpos)) != E_NOTEXIST)
|
|
{
|
|
if (rc == S_OK)
|
|
rc = E_FAIL;
|
|
goto exit02;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
/* key goes in old block. Write out the new one */
|
|
|
|
if ((rc = RcWriteBlock(qcbNew, qbthr)) != S_OK)
|
|
{
|
|
goto exit02;
|
|
}
|
|
}
|
|
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
}
|
|
|
|
|
|
/* insert new key and rec into the leaf block */
|
|
|
|
assert(btpos.iKey + cbAdd <= (SHORT)(qbthr->bth.cbBlock -
|
|
sizeof(DISK_BLOCK) + 1));
|
|
|
|
qb = (QB)(qcbLeaf->db.rgbBlock) + btpos.iKey;
|
|
|
|
QVCOPY(qb + cbAdd, qb, (LONG)qbthr->bth.cbBlock - btpos.iKey -
|
|
qcbLeaf->db.cbSlack - sizeof(DISK_BLOCK) + 1);
|
|
|
|
QVCOPY(qb, (QV)key, (LONG)cbKey);
|
|
QVCOPY(qb + cbKey, qvRec, (LONG)cbRec);
|
|
|
|
qcbLeaf->db.cKeys ++;
|
|
qcbLeaf->db.cbSlack -= cbAdd;
|
|
qcbLeaf->bFlags |= fCacheDirty;
|
|
|
|
qbthr->bth.lcEntries++;
|
|
qbthr->bth.bFlags |= fFSDirty;
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
PUBLIC HRESULT PASCAL FAR EXPORT_API RcInsertMacBrsHbt(HBT hbt, KEY key, QV qvRec)
|
|
{
|
|
QBTHR qbthr;
|
|
HF hf;
|
|
HRESULT rc;
|
|
SHORT cbAdd, cbKey, cbRec;
|
|
QCB qcbLeaf, qcbNew, qcb;
|
|
HANDLE gh;
|
|
KEY keyNew;
|
|
QB qb;
|
|
BTPOS btpos;
|
|
ERRB errb;
|
|
DWORD tmp;
|
|
|
|
|
|
if ((qbthr = _GLOBALLOCK(hbt)) == NULL)
|
|
return(E_INVALIDARG);
|
|
|
|
hf = qbthr->hf;
|
|
|
|
if ((rc = RcLookupByKeyAux(hbt, key, &btpos, NULL, TRUE)) == S_OK)
|
|
{
|
|
rc = E_DUPLICATE;
|
|
exit0:
|
|
_GLOBALUNLOCK (hbt);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
After lookup, all nodes on path from root to correct leaf are
|
|
guaranteed to be cached, with iKey valid.
|
|
*/
|
|
|
|
if (rc != E_NOTEXIST)
|
|
goto exit0;
|
|
|
|
rc = S_OK;
|
|
|
|
if (qbthr->bth.cLevels == 0)
|
|
{
|
|
|
|
// need to build a valid root block
|
|
|
|
if ((qbthr->ghCache = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
{
|
|
rc = E_OUTOFMEMORY;
|
|
goto exit0;
|
|
}
|
|
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
qcb = (QCB)qbthr->qCache;
|
|
|
|
qbthr->bth.cLevels = 1;
|
|
qbthr->bth.bkFirst = qbthr->bth.bkLast = qbthr->bth.bkRoot =
|
|
qcb->bk = BkAlloc(qbthr, &errb);
|
|
|
|
if (qcb->bk == bkNil)
|
|
{
|
|
exit01:
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALFREE(qbthr->ghCache);
|
|
qbthr->ghCache = NULL;
|
|
rc = errb;
|
|
goto exit0;
|
|
}
|
|
qcb->bFlags = fCacheDirty | fCacheValid;
|
|
qcb->db.cbSlack = qbthr->bth.cbBlock - sizeof(DISK_BLOCK) + 1
|
|
- 2 * sizeof(BK);
|
|
qcb->db.cKeys = 0;
|
|
SetBkPrev(qcb, bkNil);
|
|
SetBkNext(qcb, bkNil);
|
|
btpos.iKey = 2 * sizeof(BK);
|
|
|
|
}
|
|
else
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
cbRec = CbSizeRec(qvRec, qbthr);
|
|
cbAdd = cbKey + cbRec;
|
|
|
|
/* check to see if key and rec can fit harmoniously in a block */
|
|
|
|
if (cbAdd > qbthr->bth.cbBlock / 2)
|
|
{
|
|
rc = E_FAIL;
|
|
goto exit01;
|
|
}
|
|
|
|
qcbLeaf = QCacheBlock(qbthr, qbthr->bth.cLevels - 1);
|
|
|
|
if (cbAdd > qcbLeaf->db.cbSlack)
|
|
{
|
|
/* new key and rec don't fit in leaf: split the block */
|
|
|
|
/* create new leaf block */
|
|
|
|
if ((gh = _GLOBALALLOC(GMEM_ZEROINIT| GMEM_SHARE| GMEM_MOVEABLE,
|
|
(LONG)CbCacheBlock(qbthr))) == NULL)
|
|
{
|
|
rc = E_OUTOFMEMORY;
|
|
goto exit01;
|
|
}
|
|
qcbNew = _GLOBALLOCK(gh);
|
|
|
|
if ((qcbNew->bk = BkAlloc(qbthr, &errb)) == bkNil)
|
|
{
|
|
rc = errb;
|
|
exit02:
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
goto exit01;
|
|
}
|
|
|
|
if ((rc = RcSplitLeaf(qcbLeaf, qcbNew, qbthr)) != S_OK)
|
|
goto exit02;
|
|
|
|
keyNew = (KEY)qcbNew->db.rgbBlock + 2 * sizeof(BK);
|
|
|
|
/* insert new leaf into parent block */
|
|
|
|
if ((rc = RcInsertInternal(qcbNew->bk,
|
|
keyNew, (SHORT)(qbthr->bth.cLevels - 1), qbthr)) != S_OK)
|
|
{
|
|
goto exit02;
|
|
}
|
|
|
|
// InsertInternal can invalidate cache block pointers..
|
|
|
|
qcbLeaf = QCacheBlock(qbthr, qbthr->bth.cLevels - 1);
|
|
|
|
/* find out which leaf to put new key and rec in and cache it */
|
|
|
|
if (WCmpKey(key, keyNew, qbthr) >= 0)
|
|
{
|
|
/* key goes in new block. Write out old one and cache the new one */
|
|
if ((rc = RcWriteBlock(qcbLeaf, qbthr)) != S_OK)
|
|
goto exit02;
|
|
|
|
QVCOPY(qcbLeaf, qcbNew, (LONG)CbCacheBlock(qbthr));
|
|
|
|
/* get pos */
|
|
if ((rc = qbthr->RcScanLeaf(qcbLeaf->bk, key,
|
|
(SHORT)(qbthr->bth.cLevels - 1),
|
|
qbthr, NULL, &btpos)) != E_NOTEXIST)
|
|
{
|
|
if (rc == S_OK)
|
|
rc = E_FAIL;
|
|
goto exit02;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
/* key goes in old block. Write out the new one */
|
|
|
|
if ((rc = RcWriteBlock(qcbNew, qbthr)) != S_OK)
|
|
{
|
|
goto exit02;
|
|
}
|
|
}
|
|
|
|
_GLOBALUNLOCK(gh);
|
|
_GLOBALFREE(gh);
|
|
}
|
|
|
|
|
|
/* insert new key and rec into the leaf block */
|
|
|
|
assert(btpos.iKey + cbAdd <= (SHORT)(qbthr->bth.cbBlock -
|
|
sizeof(DISK_BLOCK) + 1));
|
|
|
|
qb = (QB)(qcbLeaf->db.rgbBlock) + btpos.iKey;
|
|
|
|
QVCOPY(qb + cbAdd, qb, (LONG)qbthr->bth.cbBlock - btpos.iKey -
|
|
qcbLeaf->db.cbSlack - sizeof(DISK_BLOCK) + 1);
|
|
|
|
tmp = GETLONG((QV)key);
|
|
QVCOPY(qb, (QV)&tmp, (LONG)cbKey);
|
|
QVCOPY(qb + cbKey, qvRec, (LONG)cbRec);
|
|
|
|
qcbLeaf->db.cKeys ++;
|
|
qcbLeaf->db.cbSlack -= cbAdd;
|
|
qcbLeaf->bFlags |= fCacheDirty;
|
|
|
|
qbthr->bth.lcEntries++;
|
|
qbthr->bth.bFlags |= fFSDirty;
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* @doc PUBLIC API
|
|
*
|
|
* @func HRESULT PASCAL FAR | RcUpdateHbt |
|
|
* Update the record for an existing key. If the key wasn't
|
|
* there already, it will not be inserted.
|
|
*
|
|
* @parm HBT | hbt |
|
|
* Handle to B-tree structure
|
|
*
|
|
* @parm KEY | key |
|
|
* key that already exists in btree
|
|
*
|
|
* @parm QV | qvRec |
|
|
* new record
|
|
*
|
|
* @rdesc E_INVALIDARG, S_OK; ERR_NOTEXIST
|
|
* args OUT: hbt - if key was in btree, it now has a new record.
|
|
*
|
|
* @comm
|
|
* Method: If the records are the same size, copy the new over
|
|
* the old. Otherwise, delete the old key/rec and insert the new.
|
|
*
|
|
***************************************************************************/
|
|
|
|
PUBLIC HRESULT PASCAL FAR EXPORT_API RcUpdateHbt(HBT hbt, KEY key, QV qvRec)
|
|
{
|
|
HRESULT rc;
|
|
QBTHR qbthr;
|
|
QB qb;
|
|
QCB qcb;
|
|
BTPOS btpos;
|
|
WORD wSizeNew,wSizeOld;
|
|
SHORT iSizeKey;
|
|
|
|
|
|
if ((qbthr = _GLOBALLOCK(hbt)) == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if ((rc = RcLookupByKey(hbt, key, &btpos, NULL)) != S_OK)
|
|
{
|
|
_GLOBALUNLOCK(hbt);
|
|
return rc;
|
|
}
|
|
|
|
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
|
|
|
|
if (qbthr->bth.cLevels <= 0 || qbthr->qCache == NULL)
|
|
return (E_ASSERT);
|
|
|
|
qcb = QCacheBlock(qbthr, qbthr->bth.cLevels - 1);
|
|
qb = qcb->db.rgbBlock + btpos.iKey;
|
|
|
|
qb += (iSizeKey=CbSizeKey((KEY)qb, qbthr, FALSE));
|
|
|
|
if ((wSizeNew=CbSizeRec(qvRec, qbthr)) != (wSizeOld=CbSizeRec(qb, qbthr)))
|
|
{
|
|
// Today's the day we do something clever:
|
|
if ((wSizeNew<wSizeOld) || ((wSizeNew>wSizeOld) && (qcb->db.cbSlack>=wSizeNew-wSizeOld)))
|
|
{
|
|
WORD wBytesAfterBlock = (WORD) (qbthr->bth.cbBlock-sizeof(qcb->db)+1 -qcb->db.cbSlack -btpos.iKey -wSizeOld -iSizeKey);
|
|
QB qb1, qb2;
|
|
qb1=qb+max(wSizeNew,wSizeOld)+wBytesAfterBlock;
|
|
qb2=(QB)(&qcb->db)+qbthr->bth.cbBlock;
|
|
assert(qb1<=qb2);
|
|
|
|
if (wBytesAfterBlock)
|
|
QVCOPY(qb+wSizeNew,qb+wSizeOld,(LONG)wBytesAfterBlock);
|
|
QVCOPY(qb, qvRec, (LONG)wSizeNew);
|
|
qcb->bFlags |= fCacheDirty;
|
|
qbthr->bth.bFlags |= fFSDirty;
|
|
qcb->db.cbSlack=qcb->db.cbSlack+wSizeOld-wSizeNew;
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
}
|
|
else
|
|
{
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
|
|
rc = RcDeleteHbt(hbt, key);
|
|
|
|
if (rc == S_OK)
|
|
{
|
|
rc = RcInsertHbt(hbt, key, qvRec);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QVCOPY(qb, qvRec, (LONG)wSizeNew);
|
|
|
|
qcb->bFlags |= fCacheDirty;
|
|
qbthr->bth.bFlags |= fFSDirty;
|
|
|
|
_GLOBALUNLOCK(qbthr->ghCache);
|
|
_GLOBALUNLOCK(hbt);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* EOF */
|