windows-nt/Source/XPSP1/NT/enduser/stuff/itircl/common/btree/btlookup.c
2020-09-26 16:20:57 +08:00

1121 lines
26 KiB
C

/*****************************************************************************
* *
* BTLOOKUP.C *
* *
* Copyright (C) Microsoft Corporation 1989 - 1994. *
* All Rights reserved. *
* *
******************************************************************************
* *
* Module Intent *
* Btree lookup and helper functions. *
* *
******************************************************************************
* *
* Current Owner: UNDONE *
* *
*****************************************************************************/
static char s_aszModule[] = __FILE__; /* For error report */
#include <mvopsys.h>
#include <orkin.h>
#include <misc.h>
#include <iterror.h>
#include <wrapstor.h>
#include <_mvutil.h>
/***************************************************************************
*
* Private Functions
*
***************************************************************************/
/***************************************************************************
*
* @doc INTERNAL
*
* @func SHORT PASCAL FAR | CbSizeRec |
* Get the size of a record.
*
* @parm QV | qRec |
* the record to be sized
*
* @parm QBTHR | qbthr |
* btree header containing record format string
*
* @rdesc size of the record in bytes
* If we've never computed the size before, we do so by looking
* at the record format string in the btree header. If the
* record is fixed size, we store the size in the header for
* next time. If it isn't fixed size, we have to look at the
* actual record to determine its size.
*
***************************************************************************/
PUBLIC SHORT PASCAL FAR CbSizeRec(QV qRec, QBTHR qbthr)
{
CHAR ch;
QCH qchFormat = qbthr->bth.rgchFormat;
SHORT cb = 0;
BOOL fFixedSize;
LPBYTE lpb;
if (qbthr->cbRecordSize)
return qbthr->cbRecordSize;
fFixedSize = TRUE;
for (qchFormat++; ch = *qchFormat; qchFormat++)
{
switch (ch)
{
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
cb += ch - '0';
break;
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
cb += ch + 10 - 'a';
break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
cb += ch + 10 - 'A';
break;
// Variable length File Offset value (minimum 3 bytes)
case FMT_VNUM_FO:
lpb=((LPBYTE)qRec)+cb;
cb++;
while ((*(lpb++))&0x80)
cb++;
fFixedSize = FALSE;
break;
case FMT_BYTE_PREFIX:
cb += sizeof(BYTE) + *((QB)qRec + cb);
fFixedSize = FALSE;
break;
case FMT_WORD_PREFIX:
cb += sizeof(SHORT) + *((QW)qRec + cb);
fFixedSize = FALSE;
break;
case FMT_SZ:
cb += (SHORT) STRLEN((QB)qRec + cb) + 1;
fFixedSize = FALSE;
break;
default:
/* error */
assert(FALSE);
break;
}
}
if (fFixedSize)
{
qbthr->cbRecordSize = cb;
}
return cb;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func BOOL PASCAL FAR | FReadBlock |
* Read a block from the btree file into the cache block.
*
* @parm QCB | qcb |
* qcb->bk - bk of block to read
* qcb->db - receives block read in from file
* qcb->bFlags - fCacheValid flag set, all others cleared
*
* @parm QBTHR | qbthr |
* qbthr->cbBlock - size of disk block to read
*
* @rdesc S_OK or other errors
*
* Notes: Doesn't know about real cache, just this block
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR FReadBlock(QCB qcb, QBTHR qbthr)
{
LONG l;
HRESULT errb;
FILEOFFSET foSeek;
if (qcb->bk >= qbthr->bth.bkEOF)
{
return E_ASSERT;
}
foSeek=FoFromBk(qcb->bk, qbthr);
if (!FoEquals(FoSeekHf(qbthr->hf, foSeek, wFSSeekSet, &errb),foSeek))
{
return (E_FILESEEK);
}
l = qbthr->bth.cbBlock;
errb = S_OK;
if (LcbReadHf(qbthr->hf, &(qcb->db), (LONG)qbthr->bth.cbBlock,
&errb) != l)
{
if (errb == S_OK)
return (E_FILEINVALID);
return errb;
}
qcb->bFlags = fCacheValid;
qcb->db.cbSlack = SWAPWORD(qcb->db.cbSlack);
qcb->db.cKeys = SWAPWORD(qcb->db.cKeys);
return (S_OK);
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT PASCAL FAR | RcWriteBlock |
* Write a cached block to a file.
*
* @parm QCB | qcb |
* qcb->db the block to write
* qcb->bk bk of block to write
*
* @parm QBTHR | qbthr |
* qbthr->hf we write to this file
*
* @rdesc S_OK or other errors
* Side Effects: Fatal exit on read or seek failure.
*
* Note: Don't reset dirty flag, because everyone who wants
* that done does it themselves. (?)
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcWriteBlock(QCB qcb, QBTHR qbthr)
{
#ifdef MOSMAP // {
// Disable function
return ERR_NOTSUPPORTED;
#else // } {
HRESULT errb;
FILEOFFSET foSeek;
if (qcb->bk >= qbthr->bth.bkEOF)
return E_ASSERT;
if ((qcb->db.cbSlack > qbthr->bth.cbBlock) || (qcb->db.cbSlack<0))
return E_ASSERT;
#if 0
if (qcb->db.cKeys*8+qcb->db.cbSlack > qbthr->bth.cbBlock)
return E_ASSERT;
#endif
foSeek=FoFromBk(qcb->bk, qbthr);
errb = S_OK;
if (!FoEquals(FoSeekHf(qbthr->hf, foSeek, wFSSeekSet, &errb),foSeek) )
{
return(errb);
}
LcbWriteHf(qbthr->hf, &(qcb->db), (LONG)qbthr->bth.cbBlock, &errb);
return errb;
#endif //}
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func QCB PASCAL FAR | QFromBk |
* Convert a BK into a pointer to a cache block. Cache the
* block at the given level, if it isn't there already.
*
* @parm BK | bk |
* BK to convert
*
* @parm SHORT | wLevel |
* btree level
* @parm QBTHR | qbthr |
* Ptr to B-tree struct. State in: btree cache is locked
*
* @rdesc pointer to the cache block, with all fields up to date
* or NULL on I/O error
* state OUT: block will be in cache at specified level; cache locked
*
***************************************************************************/
PUBLIC QCB PASCAL FAR QFromBk(BK bk, SHORT wLevel, QBTHR qbthr, PHRESULT phr)
{
QCB qcb;
HRESULT fRet;
if (wLevel < 0 || wLevel >= qbthr->bth.cLevels || bk >= qbthr->bth.bkEOF)
{
SetErrCode (phr, E_ASSERT);
return(NULL);
}
qcb = QCacheBlock(qbthr, wLevel);
if (!(qcb->bFlags & fCacheValid) || bk != qcb->bk)
{
/* requested block is not cached */
if ((qcb->bFlags & fCacheDirty) && (qcb->bFlags & fCacheValid))
{
if ((fRet = RcWriteBlock(qcb, qbthr)) != S_OK)
{
SetErrCode (phr, fRet);
return NULL;
}
}
qcb->bk = bk;
if ((fRet = FReadBlock(qcb, qbthr)) != S_OK)
{
SetErrCode (phr, fRet);
return NULL;
}
}
return qcb;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT PASCAL FAR | RcFlushCache |
* Write out dirty cache blocks
*
* @parm QBTHR | qbthr |
* qCache is locked
*
* @rdesc rc
* state OUT: btree file is up to date. cache block dirty flags reset
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcFlushCache(QBTHR qbthr)
{
#ifdef MOSMAP // {
// Disable function
return ERR_SUCCESSS;
#else // } {
SHORT i;
QB qb;
HRESULT rc = S_OK;
const SHORT iMax = qbthr->bth.cLevels;
// We need to traverse this list in reverse order so the
// nodes are actually written in numeric order
qb = qbthr->qCache + CbCacheBlock(qbthr) * (iMax - 1);
for (i = 0; i < iMax; i++, qb -= CbCacheBlock(qbthr))
{
if ((((QCB)qb)->bFlags & (fCacheDirty | fCacheValid)) ==
(fCacheValid | fCacheDirty))
{
if ((rc = RcWriteBlock((QCB)qb, qbthr)) != S_OK)
break;
((QCB)qb)->bFlags &= ~fCacheDirty;
}
}
return rc;
#endif //}
}
/***************************************************************************
*
* Public Functions
*
***************************************************************************/
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT PASCAL FAR | RcLookupByKeyAux |
* Look up a key in a btree and retrieve the data.
* state IN: cache is unlocked
*
* @parm HBT | hbt |
* btree handle
*
* @parm KEY | key |
* key we are looking up
*
* @parm QBTPOS | qbtpos |
* pointer to buffer for pos; use NULL if not wanted
*
* @parm QV | qData |
* pointer to buffer for record; NULL if not wanted
*
* @parm BOOL | fInsert |
* TRUE: if key would lie between two blocks, pos refers to proper
* place to insert it
* FALSE: pos returned will be valid unless key > all keys in btree
*
* @rdesc S_OK if found, E_NOTEXIST if not found;
* other errors like ERR_MEMORY
* key found:
* qbtpos - btpos for this key
* qData - record for this key
*
* key not found:
* qbtpos - btpos for first key > this key
* qData - record for first key > this key
*
* key not found, no keys in btree > key:
* qbtpos - invalid (qbtpos->bk == bkNil)
* qData - undefined
* state OUT: All ancestor blocks back to root are cached
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcLookupByKeyAux(HBT hbt, KEY key,
QBTPOS qbtpos, QV qData, BOOL fInsert)
{
QBTHR qbthr;
SHORT wLevel;
BK bk;
HRESULT rc;
HRESULT errb;
if (hbt == NULL)
return E_INVALIDARG;
qbthr = _GLOBALLOCK(hbt);
if (qbthr->bth.cLevels <= 0)
{
rc = E_NOTEXIST;
exit0:
_GLOBALUNLOCK(hbt);
return rc;
}
if (qbthr->ghCache == NULL)
{
if ((rc = RcMakeCache(qbthr)) != S_OK)
goto exit0;
}
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
/* Look in the top level */
for (wLevel = 0, bk = qbthr->bth.bkRoot;
bk != bkNil && wLevel < qbthr->bth.cLevels - 1; wLevel++)
{
bk = qbthr->BkScanInternal(bk, key, wLevel, qbthr, NULL, &errb);
}
if (bk == bkNil)
{
rc = E_NOTEXIST;
exit1:
_GLOBALUNLOCK(qbthr->ghCache);
goto exit0;
}
if (((rc = qbthr->RcScanLeaf(bk, key, wLevel, qbthr,
qData, qbtpos)) == E_NOTEXIST) &&
qbtpos != NULL && !fInsert)
{
QCB qcb;
errb = S_OK;
qcb = QFromBk(qbtpos->bk, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, &errb);
if (errb != S_OK)
rc = errb;
if (qcb != NULL)
{
if (qcb != NULL && qbtpos->cKey == qcb->db.cKeys)
{
if (qbtpos->bk == qbthr->bth.bkLast)
{
qbtpos->bk = bkNil;
}
else {
qbtpos->bk = SWAPLONG(BkNext(qcb));
qbtpos->cKey = 0;
qbtpos->iKey = 2 * sizeof(BK);
}
}
}
}
goto exit1;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func WORD PASCAL FAR | WGetNextNEntries |
* Get a series of keys, data, or both from btree
*
* @parm HBT | hbt | B-tree handle
*
* @parm WORD | wFlags |
* Any one of the following flags (they may be combined, in which case
* the data will be of the form [Key Flag]* (keys and flags interleaved).
*
* @flag GETNEXT_KEYS | Key fields will be returned
* @flag GETNEXT_RECS | Data records will be returned
* @flag GETNEXT_RESET | Ignore value in qbtpos and start from first entry
* @parm WORD | wNumEntries | Max number of entries to retrieve. It may
* be the case that fewer entries are returned.
*
* @parm QBTPOS | qbtpos |
* pointer to buffer containing starting point for retrieval, or NULL
* to start from the beginning and not care where we left off. Call
* <f WGetNextN> multiple times to get the next items in the series
* if <P qbtpos> is valid.
*
* @parm QV | qvBuffer
* Pointer to buffer for record retrieved data.
*
* @parm LONG | lBufSize
* Maximum number of bytes contained in buffer.
*
* @parm PHRESULT | lpErrb
* Error return code. Valid if returned value not equal to <P wNumEntries>.
*
* @rdesc Number of entries returned in buffer.
*
***************************************************************************/
WORD PASCAL FAR wGetNextNEntries(HBT hbt, WORD wFlags, WORD wNumEntries, QBTPOS qbtpos,
QV qvBuffer, LONG lBufSize, PHRESULT phr)
{
QBTHR qbthr;
BK bk;
QCB qcb;
SHORT cbKey, cbRec;
QB qb;
QB qbBuffer=(QB)qvBuffer;
HRESULT rc;
int iKeyCurrent;
int cKey;
WORD wEntriesFilled=0;
if (hbt == NULL)
{ SetErrCode(phr,E_INVALIDARG);
return wEntriesFilled;
}
qbthr = _GLOBALLOCK(hbt);
if (qbthr->bth.lcEntries == (LONG)0)
{
SetErrCode(phr, E_NOTEXIST);
exit0:
_GLOBALUNLOCK(hbt);
return wEntriesFilled;
}
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
if (qbthr->ghCache == NULL)
{
if ((rc = RcMakeCache(qbthr)) != S_OK)
{
SetErrCode(phr, rc);
goto exit0;
}
}
if ((!qbtpos) || (wFlags&GETNEXT_RESET)) // Start from first
{ if ((bk = qbthr->bth.bkFirst) == bkNil)
{
SetErrCode(phr, E_ASSERT);
exit1:
_GLOBALUNLOCK(qbthr->ghCache);
goto exit0;
}
if ((qcb = QFromBk(bk, (SHORT)(qbthr->bth.cLevels - 1), qbthr,
phr)) == NULL)
{
goto exit1;
}
iKeyCurrent=2 * sizeof(BK);
qb = qcb->db.rgbBlock + iKeyCurrent;
cKey=0;
}
else // Start from qbtpos
{
if (!FValidPos(qbtpos))
{
SetErrCode(phr,E_ASSERT);
goto exit0;
}
bk=qbtpos->bk;
if ((qcb = QFromBk(bk, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, phr)) == NULL)
{
goto exit1;
}
if (qbtpos->cKey==qcb->db.cKeys)
{
SetErrCode(phr, E_NOTEXIST);
goto exit1;
}
assert(qbtpos->cKey < qcb->db.cKeys);
assert(qbtpos->cKey >= 0);
assert(qbtpos->iKey >= 2 * sizeof(BK));
assert(qbtpos->iKey <= (SHORT)(qbthr->bth.cbBlock - sizeof(DISK_BLOCK)));
cKey=qbtpos->cKey;
iKeyCurrent=qbtpos->iKey;
qb = qcb->db.rgbBlock + iKeyCurrent;
}
// keep getting data and filling, and fix qbtpos to next valid spot
while (wNumEntries--)
{
cbKey = CbSizeKey((KEY)qb, qbthr, TRUE);
if (wFlags&GETNEXT_KEYS)
{
if (cbKey>lBufSize)
{
break;
}
QVCOPY(qbBuffer, qb,(LONG)cbKey);
qbBuffer+=cbKey;
lBufSize-=cbKey;
}
iKeyCurrent+=cbKey;
qb+=cbKey;
cbRec = CbSizeRec(qb, qbthr);
if (wFlags&GETNEXT_RECS)
{
if (cbRec>lBufSize)
{ iKeyCurrent-=cbKey;
lBufSize-=cbKey;
break;
}
QVCOPY(qbBuffer,qb,(LONG)cbRec);
qbBuffer+=cbRec;
lBufSize-=cbRec;
}
iKeyCurrent+=cbRec;
qb+=cbRec;
wEntriesFilled++;
cKey++;
if (cKey>=qcb->db.cKeys) // Must advance to next block!
{
BK bkNew = SWAPLONG(BkNext(qcb));
if (bkNew == bkNil) // Back up to last key in btree
{ //cKey--;
//iKeyCurrent-=cbKey+cbRec;
break;
}
if ((qcb = QFromBk(bkNew, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, phr)) == NULL)
{
goto exit1;
}
iKeyCurrent=2 * sizeof(BK);
qb = qcb->db.rgbBlock + iKeyCurrent;
cKey=0;
bk=bkNew;
}
}
if (qbtpos != NULL)
{
qbtpos->bk = bk;
qbtpos->iKey = iKeyCurrent;
qbtpos->cKey = cKey;
}
goto exit1;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT PASCAL FAR | RcFirstHbt |
* Get first key and record from btree.
*
* @parm HBT | hbt | B-tree handle
*
* @parm KEY | key |
* points to buffer big enough to hold a key (256 bytes is more
* than enough)
*
* @parm QV | qvRec
* pointer to buffer for record or NULL if not wanted
*
* @parm QBTPOS | qbtpos |
* pointer to buffer for btpos or NULL if not wanted
*
* @rdesc S_OK if anything found, else error code.
* key - key copied here
* qvRec - record copied here
* qbtpos- btpos of first entry copied here
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcFirstHbt(HBT hbt, KEY key, QV qvRec, QBTPOS qbtpos)
{
QBTHR qbthr;
BK bk;
QCB qcb;
SHORT cbKey, cbRec;
QB qb;
HRESULT rc;
HRESULT errb;
if (hbt == NULL)
return E_INVALIDARG;
qbthr = _GLOBALLOCK(hbt);
if (qbthr->bth.lcEntries == (LONG)0)
{
rc = E_NOTEXIST;
exit0:
_GLOBALUNLOCK(hbt);
return rc;
}
if ((bk = qbthr->bth.bkFirst) == bkNil)
{
rc = E_ASSERT;
goto exit0;
}
if (qbthr->ghCache == NULL)
{
if ((rc = RcMakeCache(qbthr)) != S_OK)
goto exit0;
}
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
if ((qcb = QFromBk(bk, (SHORT)(qbthr->bth.cLevels - 1), qbthr,
&errb)) == NULL)
{
rc = errb;
exit1:
_GLOBALUNLOCK(qbthr->ghCache);
goto exit0;
}
qb = qcb->db.rgbBlock + 2 * sizeof(BK);
cbKey = CbSizeKey((KEY)qb, qbthr, TRUE);
if ((QV)key != NULL) QVCOPY((QV)key, qb, (LONG)cbKey);
qb += cbKey;
cbRec = CbSizeRec(qb, qbthr);
if (qvRec != NULL)
QVCOPY(qvRec, qb, (LONG)cbRec);
if (qbtpos != NULL)
{
qbtpos->bk = bk;
qbtpos->iKey = 2 * sizeof(BK);
qbtpos->cKey = 0;
}
rc = S_OK;
goto exit1;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT PASCAL FAR | RcLastHbt |
* Get last key and record from btree.
*
* @parm HBT | hbt | B-tree handle
*
* @parm KEY | key |
* points to buffer big enough to hold a key (256 bytes
* is more than enough)
*
* @parm QV | qvRec |
* points to buffer big enough for record
*
* @rdesc S_OK if anything found, else error code.
* key - key copied here
* qvRec - record copied here
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcLastHbt(HBT hbt, KEY key, QV qvRec, QBTPOS qbtpos)
{
QBTHR qbthr;
BK bk;
QCB qcb;
SHORT cbKey, cbRec, cKey;
QB qb;
HRESULT rc;
HRESULT errb;
if (hbt == NULL)
return E_INVALIDARG;
qbthr = _GLOBALLOCK(hbt);
if (qbthr->bth.lcEntries == (LONG)0)
{
rc = E_NOTEXIST;
exit0:
_GLOBALUNLOCK(hbt);
return rc;
}
if ((bk = qbthr->bth.bkLast) ==bkNil)
{
rc = E_ASSERT;
goto exit0;
}
if (qbthr->ghCache == NULL)
{
if ((rc = RcMakeCache(qbthr)) != S_OK)
goto exit0;
}
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
if ((qcb = QFromBk(bk, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, &errb)) == NULL)
{
rc = errb;
exit1:
_GLOBALUNLOCK(qbthr->ghCache);
goto exit0;
}
qb = qcb->db.rgbBlock + 2 * sizeof(BK);
for (cKey = 0; cKey < qcb->db.cKeys - 1; cKey++)
{
qb += CbSizeKey((KEY)qb, qbthr, TRUE);
qb += CbSizeRec(qb, qbthr);
}
cbKey = CbSizeKey((KEY)qb, qbthr, FALSE);
if ((QV)key != NULL)
QVCOPY((QV)key, qb, (LONG)cbKey); // decompress
cbRec = CbSizeRec(qb + cbKey, qbthr);
if (qvRec != NULL)
QVCOPY(qvRec, qb + cbKey, (LONG)cbRec);
if (qbtpos != NULL)
{
qbtpos->bk = bk;
qbtpos->iKey = (int)(qb - (QB)qcb->db.rgbBlock);
qbtpos->cKey = cKey;
}
rc = S_OK;
goto exit1;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT FAR PASCAL | RcLookupByPos |
* Map a pos into a key and rec (both optional).
*
* @parm HBT | hbt |
* the btree
*
* @parm QBTPOS | qbtpos
* pointer to pos
*
* @rdesc S_OK or errors
* key - if not (KEY)NULL, key copied here, not to exceed iLen
* qRec - if not NULL, record copied here
*
***************************************************************************/
PUBLIC HRESULT FAR PASCAL RcLookupByPos(HBT hbt, QBTPOS qbtpos,
KEY key, int iLen, QV qRec)
{
QBTHR qbthr;
QCB qcbLeaf;
QB qb;
HRESULT rc;
HRESULT errb;
/* Sanity check */
if (!FValidPos(qbtpos))
return E_ASSERT;
if (hbt == NULL)
return E_INVALIDARG;
qbthr = _GLOBALLOCK(hbt);
if (qbthr->bth.cLevels <= 0)
{
rc = E_NOTEXIST;
exit0:
_GLOBALUNLOCK(hbt);
return rc;
}
if (qbthr->ghCache == NULL)
{
if ((rc = RcMakeCache(qbthr)) != S_OK)
goto exit0;
}
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache);
if ((qcbLeaf = QFromBk(qbtpos->bk, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, &errb)) == NULL)
{
rc = errb;
exit1:
_GLOBALUNLOCK(qbthr->ghCache);
goto exit0;
}
assert(qbtpos->cKey < qcbLeaf->db.cKeys);
assert(qbtpos->cKey >= 0);
assert(qbtpos->iKey >= 2 * sizeof(BK));
assert(qbtpos->iKey <= (SHORT)(qbthr->bth.cbBlock - sizeof(DISK_BLOCK)));
qb = qcbLeaf->db.rgbBlock + qbtpos->iKey;
if (key != (KEY)NULL)
{
QVCOPY((QV)key, qb, (LONG)min(iLen,CbSizeKey((KEY)qb, qbthr, FALSE))); /* need to decompress */
qb += CbSizeKey(key, qbthr, TRUE);
}
if (qRec != NULL)
{
QVCOPY(qRec, qb, (LONG)CbSizeRec(qb, qbthr));
}
rc = S_OK;
goto exit1;
}
/***************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT PASCAL FAR | RcNextPos |
* get the next record from the btree
* Next means the next key lexicographically from the key
* most recently inserted or looked up
* Won't work if we looked up a key and it wasn't there
*
* STATE IN: a record has been read from or written to the file
* since the last deletion
*
*
* @parm HBT | hbt | B-tree handle
*
* @rdesc S_OK; E_NOTEXIST if no successor record
* args OUT: key - next key copied to here
* qvRec - record gets copied here
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcNextPos(HBT hbt, QBTPOS qbtposIn, QBTPOS qbtposOut)
{
LONG l;
return RcOffsetPos(hbt, qbtposIn, (LONG)1, &l, qbtposOut);
}
/***************************************************************************
*
* @dos INTERNAL
*
* @func HRESULT PASCAL FAR | RcOffsetPos |
* pos, offset from the previous pos by specified amount.
* If not possible (i.e. prev of first) return real amount offset
* and a pos.
*
* @parm HBT | hbt |
* handle to btree
*
* @parm QBTPOS | qbtposIn |
* position we want an offset from
*
* @parm LONGD | lcOffset |
* amount to offset (+ or - OK)
*
* @rdesc rc
* args OUT: qbtposOut - new position offset by *qcRealOffset
* *qlcRealOffset - equal to lcOffset if legal, otherwise
* as close as is legal
*
***************************************************************************/
PUBLIC HRESULT PASCAL FAR RcOffsetPos(HBT hbt, QBTPOS qbtposIn,
LONG lcOffset, QL qlcRealOffset, QBTPOS qbtposOut)
{
QBTHR qbthr;
HRESULT rc = S_OK;
SHORT c;
LONG lcKey, lcDelta = (LONG)0;
QCB qcb;
BK bk;
QB qb;
HRESULT errb;
if (!FValidPos(qbtposIn))
return E_ASSERT;
if (hbt == NULL || qlcRealOffset == NULL)
return E_INVALIDARG;
bk = qbtposIn->bk;
qbthr = _GLOBALLOCK(hbt);
if (qbthr->bth.cLevels <= 0)
{
rc = E_NOTEXIST;
exit0:
_GLOBALUNLOCK(hbt);
return rc;
}
if (qbthr->ghCache == NULL)
{
if ((rc = RcMakeCache(qbthr)) != S_OK)
{
goto exit0;
}
}
qbthr->qCache = _GLOBALLOCK(qbthr->ghCache); // >>>>what if no entries??
if ((qcb = QFromBk(qbtposIn->bk,
(SHORT)(qbthr->bth.cLevels - 1), qbthr, &errb)) == NULL)
{
rc = errb;
exit1:
_GLOBALUNLOCK(qbthr->ghCache);
goto exit0;
}
lcKey = qbtposIn->cKey + lcOffset;
/* chase prev to find the right block */
while (lcKey < 0)
{
bk = SWAPLONG(BkPrev(qcb));
if (bk == bkNil)
{
bk = qcb->bk;
lcDelta = lcKey;
lcKey = 0;
break;
}
if ((qcb = QFromBk(bk, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, &errb)) == NULL)
{
rc = errb;
goto exit1;
}
lcKey += qcb->db.cKeys;
}
/* chase next to find the right block */
while (lcKey >= qcb->db.cKeys)
{
lcKey -= qcb->db.cKeys;
bk = SWAPLONG(BkNext(qcb));
if (bk == bkNil)
{
bk = qcb->bk;
lcDelta = lcKey + 1;
lcKey = qcb->db.cKeys - 1;
break;
}
if ((qcb = QFromBk(bk, (SHORT)(qbthr->bth.cLevels - 1),
qbthr, &errb)) == NULL)
{
rc = errb;
goto exit1;
}
}
if (bk == qbtposIn->bk && lcKey >= qbtposIn->cKey)
{
c = (SHORT) qbtposIn->cKey;
qb = qcb->db.rgbBlock + qbtposIn->iKey;
}
else
{
c = 0;
qb = qcb->db.rgbBlock + 2 * sizeof(BK);
}
while ((LONG)c < lcKey)
{
qb += CbSizeKey((KEY)qb, qbthr, TRUE);
qb += CbSizeRec(qb, qbthr);
c++;
}
if (qbtposOut != NULL)
{
qbtposOut->bk = bk;
qbtposOut->iKey = (int)(qb - (QB)qcb->db.rgbBlock);
qbtposOut->cKey = c;
}
*qlcRealOffset = lcOffset - lcDelta;
rc = (lcDelta ? E_NOTEXIST: S_OK);
goto exit1;
}
/* EOF */