2025-04-27 07:49:33 -04:00

1947 lines
49 KiB
C

#include "config.h"
#include <string.h>
#include "daedef.h"
#include "pib.h"
#include "ssib.h"
#include "page.h"
#include "fcb.h"
#include "fucb.h"
#include "stapi.h"
#include "dirapi.h"
#include "nver.h"
#include "util.h"
DeclAssertFile; /* Declare file name for assert macros */
LOCAL BOOL FBTThere( FUCB *pfucb );
LOCAL INT IbsonBTFrac( FUCB *pfucb, CSR *pcsr, DIB *pdib );
/* returns fTrue if node is potentially there and fFalse if
/* node is not potentially there. A node is potentially there
/* if it can be there as a result of a transaction committ or
/* a transaction rollback.
/**/
LOCAL BOOL FBTPotThere( FUCB *pfucb )
{
SSIB *pssib = &pfucb->ssib;
BOOL fDelete = FNDDeleted( *pssib->line.pb );
VS vs;
SRID srid;
BOOL fPotThere;
AssertNDGet( pfucb, PcsrCurrent( pfucb )->itag );
/* if session cursor isolation model is not dirty and node
/* has version, then call version store for appropriate version.
/**/
if ( FNDVersion( *pssib->line.pb ) && !FPIBDirty( pfucb->ppib ) )
{
NDGetBookmark( pfucb, &srid );
vs = VsVERCheck( pfucb, srid );
fPotThere = FVERPotThere( vs, fDelete );
return fPotThere;
}
return !fDelete;
}
LOCAL BOOL FBTThere( FUCB *pfucb )
{
SSIB *pssib = &pfucb->ssib;
AssertNDGet( pfucb, PcsrCurrent( pfucb )->itag );
/* if session cursor isolation model is not dirty and node
/* has version, then call version store for appropriate version.
/**/
if ( FNDVersion( *pssib->line.pb ) && !FPIBDirty( pfucb->ppib ) )
{
NS ns;
SRID srid;
NDGetBookmark( pfucb, &srid );
ns = NsVERAccessNode( pfucb, srid );
return ( ns == nsVersion || ns == nsVerInDB || ns == nsDatabase && !FNDDeleted( *pssib->line.pb ) );
}
return !FNDDeleted( *pssib->line.pb );
}
/* return fTrue this session can modify current node with out write conflict.
/**/
BOOL FBTMostRecent( FUCB *pfucb )
{
SSIB *pssib = &pfucb->ssib;
AssertNDGet( pfucb, PcsrCurrent( pfucb )->itag );
if ( FNDVersion( *pssib->line.pb ) )
{
VS vs;
SRID srid;
NDGetBookmark( pfucb, &srid );
vs = VsVERCheck( pfucb, srid );
return ( vs != vsUncommittedByOther );
}
Assert( !FNDDeleted( *pssib->line.pb ) );
return fTrue;
}
/* ErrBTGet returns an error is the current node
/* is not there for the caller, and caches the line.
/**/
ERR ErrBTGet( FUCB *pfucb, CSR *pcsr )
{
ERR err;
SSIB *pssib = &pfucb->ssib;
if ( !FReadAccessPage( pfucb, pcsr->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
}
NDGet( pfucb, pcsr->itag );
if ( FNDVersion( *pssib->line.pb ) && !FPIBDirty( pfucb->ppib ) )
{
NS ns;
SRID srid;
NDGetBookmark( pfucb, &srid );
ns = NsVERAccessNode( pfucb, srid );
if ( ns == nsDatabase )
{
if ( FNDDeleted( *(pfucb->ssib.line.pb) ) )
return JET_errRecordDeleted;
}
else if ( ns == nsInvalid )
{
return JET_errRecordDeleted;
}
else
return JET_errSuccess;
}
if ( FNDDeleted( *(pfucb->ssib.line.pb) ) )
return JET_errRecordDeleted;
return JET_errSuccess;
}
/* ErrBTGetNode returns an error if the current node is not there for
/* the caller, and otherwise caches the line, data and key for the
/* verion of the node for the caller.
/**/
ERR ErrBTGetNode( FUCB *pfucb, CSR *pcsr )
{
ERR err;
SSIB *pssib = &pfucb->ssib;
Assert( pcsr->csrstat == csrstatOnCurNode ||
pcsr->csrstat == csrstatOnFDPNode ||
pcsr->csrstat == csrstatOnDataRoot ||
pcsr->csrstat == csrstatBeforeCurNode ||
pcsr->csrstat == csrstatAfterCurNode );
if ( !FReadAccessPage( pfucb, pcsr->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
}
NDGet( pfucb, pcsr->itag );
if ( FNDVersion( *pssib->line.pb ) && !FPIBDirty( pfucb->ppib ) )
{
NS ns;
SRID srid;
NDGetBookmark( pfucb, &srid );
ns = NsVERAccessNode( pfucb, srid );
if ( ns == nsVersion )
{
/* got data but must now get key.
/**/
NDGetKey( pfucb );
}
else if ( ns == nsDatabase )
{
if ( FNDDeleted( *(pfucb->ssib.line.pb) ) )
return JET_errRecordDeleted;
NDGetNode( pfucb );
}
else if ( ns == nsInvalid )
{
return JET_errRecordDeleted;
}
else
{
Assert( ns == nsVerInDB );
NDGetNode( pfucb );
}
}
else
{
if ( FNDDeleted( *pssib->line.pb ) )
return JET_errRecordDeleted;
NDGetNode( pfucb );
}
return JET_errSuccess;
}
#ifdef DEBUG
VOID AssertBTGetNode( FUCB *pfucb, CSR *pcsr )
{
SSIB *pssib = &pfucb->ssib;
NS ns;
SRID srid;
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
AssertNDGet( pfucb, pcsr->itag );
Assert( CbNDKey( pssib->line.pb ) == pfucb->keyNode.cb );
Assert( CbNDKey( pssib->line.pb ) == 0 ||
PbNDKey( pssib->line.pb ) == pfucb->keyNode.pb );
Assert( FNDVerDel( *pssib->line.pb ) ||
CbNDData( pssib->line.pb, pssib->line.cb ) == pfucb->lineData.cb );
Assert( FNDVerDel( *pssib->line.pb ) ||
pfucb->lineData.cb == 0 ||
PbNDData( pssib->line.pb ) == pfucb->lineData.pb );
if ( FNDVersion( *pssib->line.pb ) && !FPIBDirty( pfucb->ppib ) )
{
LINE lineData;
lineData.pb = pfucb->lineData.pb;
lineData.cb = pfucb->lineData.cb;
NDGetBookmark( pfucb, &srid );
Assert( pcsr->bm == srid );
ns = NsVERAccessNode( pfucb, srid );
Assert( ns != nsDatabase || !FNDDeleted( *(pfucb->ssib.line.pb) ) );
Assert( ns != nsInvalid );
if ( ns == nsDatabase )
NDGetNode( pfucb );
// Assert( lineData.pb == pfucb->lineData.pb );
Assert( lineData.cb == pfucb->lineData.cb );
}
else
{
Assert( !FNDDeleted( *(pfucb->ssib.line.pb) ) );
}
return;
}
#endif
LOCAL INLINE ERR ErrBTIMoveToFather( FUCB *pfucb )
{
ERR err;
Assert( PcsrCurrent( pfucb ) != pcsrNil );
Assert( FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
/* allocate new CSR
/**/
CallR( ErrFUCBNewCSR( pfucb ) );
PcsrCurrent( pfucb )->pgno = PcsrCurrent( pfucb )->pcsrPath->pgno;
PcsrCurrent( pfucb )->itagFather = PcsrCurrent( pfucb )->pcsrPath->itag;
NDGet( pfucb, PcsrCurrent( pfucb )->itagFather );
return err;
}
/* if a seek land on the first node of a page with a key larger
/* than the search key or on the last node of a page with the key
/* smaller than the search key, then we must move to previous or
/* next pages, respectively, to look for the search key node.
/* If found equal or less or greater respectively, then done.
/**/
LOCAL INLINE ERR ErrBTIMoveToSeek( FUCB *pfucb, DIB *pdib, BOOL fNext )
{
ERR err;
INT s = fNext ? -1 : 1;
INT sLimit = fNext ? 1 : -1;
forever
{
err = ErrBTNextPrev( pfucb, PcsrCurrent( pfucb ), fNext, pdib );
if ( err < 0 )
{
if ( err == JET_errNoCurrentRecord )
{
Call( ErrBTNextPrev( pfucb, PcsrCurrent( pfucb ), !fNext, pdib ) );
break;
}
goto HandleError;
}
s = CmpStKey( StNDKey( pfucb->ssib.line.pb ), pdib->pkey );
if ( s == 0 )
{
err = JET_errSuccess;
goto HandleError;
}
/* if s is same sign as limit then break
/**/
if ( s * sLimit > 0 )
{
Assert( s < 0 && sLimit == -1 || s > 0 && sLimit == 1 );
break;
}
}
Assert( s != 0 );
err = ( s > 0 ) ? wrnNDFoundGreater : wrnNDFoundLess;
HandleError:
return err;
}
LOCAL INLINE ERR ErrBTIMoveToReplace( FUCB *pfucb, KEY *pkey, PGNO pgno, INT itag )
{
ERR err;
INT s;
SSIB *pssib = &pfucb->ssib;
CSR *pcsr = PcsrCurrent( pfucb );
DIB dibT = { 0, NULL, fDIRAllNode };
Assert( itag >= 0 && itag < ctagMax );
Assert( pgno != pgnoNull );
/* determine if we seeked high of key, low of key or in key range.
/**/
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
/* if not found greater then move forward in key range looking
/* for node to replace. Stop searching forward if keys greater
/* than seek key.
/**/
if ( s <= 0 )
{
do
{
err = ErrBTNextPrev( pfucb, pcsr, fTrue, &dibT );
if ( err < 0 )
{
if ( err != JET_errNoCurrentRecord )
goto HandleError;
break;
}
if ( pcsr->pgno == pgno && pcsr->itag == itag )
{
return JET_errSuccess;
}
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
}
while ( s <= 0 );
}
/* found greater or ran out of nodes. Now move previous until
/* node to replace found. Since node was not found greater, it
/* must be found on move previous.
/**/
do
{
Call( ErrBTNextPrev( pfucb, pcsr, fFalse, &dibT ) );
Assert( CmpStKey( StNDKey( pssib->line.pb ), pkey ) >= 0 );
}
while ( pcsr->pgno != pgno || pcsr->itag != itag );
err = JET_errSuccess;
HandleError:
return err;
}
/* moves to next/prev node which is equal to or greater/less than the
/* given key. The only flag read is fDIRReplaceDuplicate which
/* causes currency to held on a duplicate key if found.
/**/
LOCAL INLINE ERR ErrBTIMoveToInsert( FUCB *pfucb, KEY *pkey, INT fFlags )
{
ERR err;
CSR *pcsr = PcsrCurrent( pfucb );
SSIB *pssib = &pfucb->ssib;
INT s;
PGNO pgno;
DIB dib;
BOOL fDuplicate;
/* if tree is empty then pcsr->itag will be itagNil, and correct
/* insert position has been found.
/**/
if ( pcsr->itag == itagNil )
{
return JET_errSuccess;
}
AssertNDGet( pfucb, pcsr->itag );
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
/* The common case for insert, is inserting a new largest node. This
/* case is shown by a seek to the last node, of the last page, where
/* the key found is less than the insert key. Since this case is the
/* most common, it must be handled the most efficiently.
/**/
if ( s < 0 )
{
PgnoNextFromPage( pssib, &pgno );
if ( pgno == pgnoNull )
{
NDGet( pfucb, pcsr->itagFather );
if ( pcsr->ibSon == CbNDSon( pssib->line.pb ) - 1 )
{
/* node found has key less than insert key, so move
/* to next virtual greater node for insert.
/**/
pcsr->ibSon++;
err = wrnNDFoundGreater;
return err;
}
}
}
#if 0
/* the next most common case is that we landed in the middle of
/* a page in a correct position. We found greater and are not
/* on the last son or first son.
/**/
if ( s > 0 && pcsr->ibSon > 0 )
{
NDGet( pfucb, itagFOP );
if ( pcsr->ibSon < CbNDSon( pssib->line.pb ) )
{
err = wrnNDFoundGreater;
return err;
}
}
#endif
/* set DIB for movement over potential nodes.
/**/
dib.fFlags = fDIRPotentialNode;
/* if found greater or equal, then move previous until found equal
/* or less. This must be done to check for any nodes with insert key.
/* Only potential nodes need be considered.
/*
/* Note that even if we land via seek on a duplicate, the node
/* is not necessarily there.
/**/
if ( s >= 0 )
{
do
{
err = ErrBTNextPrev( pfucb, pcsr, fFalse, &dib );
if ( err < 0 )
{
if ( err == JET_errNoCurrentRecord )
{
s = -1;
break;
}
goto HandleError;
}
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
}
while ( s > 0 );
}
/* initialize fDuplicate
/**/
fDuplicate = ( s == 0 );
/* set DIB for movement over all nodes
/**/
dib.fFlags = fDIRAllNode;
/* move next until find greater
/**/
do
{
err = ErrBTNextPrev( pfucb, pcsr, fTrue, &dib );
if ( err < 0 )
{
if ( err == JET_errNoCurrentRecord )
{
/* may have moved to empty page.
/**/
s = 1;
break;
}
goto HandleError;
}
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
if ( s == 0 && FBTPotThere( pfucb ) )
{
fDuplicate = fTrue;
}
}
while ( s <= 0 );
Assert( s > 0 );
/* Need to move previous to duplicate if fDIRReplaceDuplicate
/* flag set.
/**/
if ( ( fDuplicate && ( fFlags & fDIRReplaceDuplicate ) ) )
{
/* set DIB for movement over potential nodes.
/**/
dib.fFlags = fDIRPotentialNode;
CallS( ErrBTNextPrev( pfucb, pcsr, fFalse, &dib ) );
Assert( CmpStKey( StNDKey( pssib->line.pb ), pkey ) == 0 );
s = 0;
}
else if ( err == wrnDIREmptyPage && pcsr->ibSon == 0 )
{
dib.fFlags = fDIRAllNode | fDIRAllPage;
err = ErrBTNextPrev( pfucb, pcsr, fFalse, &dib );
Assert( err == JET_errSuccess || err == wrnDIREmptyPage );
/* node may have been inserted. If found then check for
/* duplicate.
/**/
if ( err == JET_errSuccess )
{
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
if ( s == 0 && FBTPotThere( pfucb ) )
{
fDuplicate = fTrue;
}
}
else
{
s = 1;
}
}
Assert( s >= 0 );
Assert( ( fFlags & fDIRReplaceDuplicate ) || s > 0 );
if ( s == 0 )
err = JET_errSuccess;
else
err = wrnNDFoundGreater;
/* check for illegal duplicate key.
/**/
if ( fDuplicate && !( fFlags & fDIRDuplicate ) )
err = JET_errKeyDuplicate;
HandleError:
return err;
}
ERR ErrBTDown( FUCB *pfucb, DIB *pdib )
{
ERR err;
ERR errPos;
CSR *pcsr;
SSIB *pssib = &pfucb->ssib;
INT s;
INT ctagSon = 0;
BOOL fMoveToSeek = fFalse;
/* search down the tree from father
/**/
CallR( ErrBTIMoveToFather( pfucb ) );
pcsr = PcsrCurrent( pfucb );
/* tree may be empty
/**/
if ( FNDNullSon( *pssib->line.pb ) )
{
err = JET_errRecordNotFound;
goto HandleError;
}
/* search down the tree from father.
/* set pssib->itag for invisible son traversal.
/**/
if ( !FNDVisibleSons( *pssib->line.pb ) )
{
/* seek through invisible sons.
/**/
do
{
/* get child page from intrisic page pointer
/* if son count is 1 or from page pointer node
/**/
if ( pcsr->itagFather != itagFOP && CbNDSon( pssib->line.pb ) == 1 )
{
/* if non-FDP page, SonTable of Intrinsic son FOP must be four bytes
/**/
AssertNDIntrinsicSon( pssib->line.pb, pssib->line.cb );
pcsr->pgno = PgnoNDOfPbSon( pssib->line.pb );
}
else
{
switch ( pdib->pos )
{
case posDown:
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
NDSeekSon( pfucb, pcsr, pdib->pkey, fDIRReplace );
break;
case posFirst:
NDMoveFirstSon( pfucb, pcsr );
break;
case posLast:
NDMoveLastSon( pfucb, pcsr );
break;
default:
{
Assert( pdib->pos == posFrac );
pcsr->ibSon = IbsonBTFrac( pfucb, pcsr, pdib );
CallS( ErrNDMoveSon( pfucb, pcsr ) );
}
}
pcsr->pgno = *(PGNO UNALIGNED *)PbNDData( pssib->line.pb );
}
/* get child page father node
/**/
Call( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
NDGet( pfucb, itagFOP );
pcsr->itagFather = itagFOP;
}
while ( !FNDVisibleSons( *pssib->line.pb ) );
}
/* down to visible son
/**/
if ( FNDSon( *pssib->line.pb ) )
{
ctagSon = CbNDSon( pssib->line.pb );
switch ( pdib->pos )
{
case posDown:
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
NDSeekSon( pfucb, pcsr, pdib->pkey, fDIRReplace );
break;
case posFirst:
NDMoveFirstSon( pfucb, pcsr );
break;
case posLast:
NDMoveLastSon( pfucb, pcsr );
break;
default:
{
Assert( pdib->pos == posFrac );
pcsr->ibSon = IbsonBTFrac( pfucb, pcsr, pdib );
CallS( ErrNDMoveSon( pfucb, pcsr ) );
}
}
}
else
{
/* must move to seek
/**/
fMoveToSeek = fTrue;
/* if we land on an empty page and there are no next previous
/* nodes. What if the tree is empty. We must first reverse
/* direction and if no node is found then return an empty tree
/* error code. The empty tree error code should be the same
/* regardless of the size of the tree.
/**/
err = ErrBTNextPrev( pfucb, pcsr, pdib->pos != posLast, pdib );
if ( err == JET_errNoCurrentRecord )
Call( ErrBTNextPrev( pfucb, pcsr, pdib->pos == posLast, pdib ) );
NDGet( pfucb, itagFOP );
/* get right ctagSon */
ctagSon = CbNDSon( pssib->line.pb );
/* adjust pssib line back to the landed node */
NDGet( pfucb, PcsrCurrent( pfucb )->itag );
}
/* we have landed on a visible node
/**/
if ( pdib->pos == posDown )
{
s = CmpStKey( StNDKey( pssib->line.pb ), pdib->pkey );
if ( s == 0 )
errPos = JET_errSuccess;
else
{
if ( s < 0 )
errPos = wrnNDFoundLess;
else
errPos = wrnNDFoundGreater;
/* if on last node in page and found less, or if landed on
/* first node in page and found greater, move next or previous
/* to look for node with search key. These anomalies can
/* occur during update seek normally, and during read seek
/* when partial split pages are encountered.
/**/
Assert( pcsr->ibSon >= 0 && pcsr->ibSon < ctagSon );
Assert( errPos == wrnNDFoundGreater &&
pcsr->ibSon == 0 || pcsr->ibSon <= ( ctagSon - 1 ) );
if ( fMoveToSeek ||
( errPos == wrnNDFoundLess && pcsr->ibSon == ( ctagSon - 1 ) ) ||
( errPos == wrnNDFoundGreater && pcsr->ibSon == 0 ) )
{
Call( ErrBTIMoveToSeek( pfucb, pdib, errPos == wrnNDFoundLess ) );
errPos = err;
}
}
}
else
{
errPos = JET_errSuccess;
}
if ( !FBTThere( pfucb ) )
{
if ( pdib->pos == posDown )
{
/* if current node is not there for us then move to next node.
/* if no next node then move to previous node.
/* if no previous node then return error.
/**/
err = ErrBTNextPrev( pfucb, pcsr, fTrue, pdib );
if ( err < 0 && err != JET_errNoCurrentRecord )
goto HandleError;
if ( err == JET_errNoCurrentRecord )
{
Call( ErrBTNextPrev( pfucb, pcsr, fFalse, pdib ) );
}
/* preferentially land on lesser key value node
/**/
if ( CmpStKey( StNDKey( pssib->line.pb ), pdib->pkey ) > 0 )
{
err = ErrBTNextPrev( pfucb, pcsr, fFalse, pdib );
if ( err == JET_errNoCurrentRecord )
{
CallS( ErrBTNextPrev( pfucb, pcsr, fTrue, pdib ) );
err = JET_errSuccess;
}
}
/* reset errPos for new node location
/**/
if ( FKeyNull( pdib->pkey ) )
{
errPos = JET_errSuccess;
}
else
{
s = CmpStKey( StNDKey( pssib->line.pb ), pdib->pkey );
if ( s > 0 )
errPos = wrnNDFoundGreater;
else if ( s < 0 )
errPos = wrnNDFoundLess;
else
errPos = JET_errSuccess;
Assert( s != 0 || errPos == JET_errSuccess );
}
Assert( err != JET_errKeyBoundary && err != JET_errPageBoundary );
if ( err == JET_errNoCurrentRecord )
{
/* move previous
/**/
Call( ErrBTNextPrev( pfucb, pcsr, fFalse, pdib ) );
errPos = wrnNDFoundLess;
}
else if ( err < 0 )
goto HandleError;
}
else
{
err = ErrBTNextPrev( pfucb, pcsr, pdib->pos != posLast, pdib );
if ( err == JET_errNoCurrentRecord )
{
/* if fractional positioning, then try to
/* move previous to valid node.
/**/
if ( pdib->pos == posFrac )
{
err = ErrBTNextPrev( pfucb, pcsr, fFalse, pdib );
}
else
err = JET_errRecordNotFound;
}
if ( err < 0 )
goto HandleError;
}
}
Assert( errPos >= 0 );
FUCBResetStore( pfucb );
return errPos;
HandleError:
BTUp( pfucb );
if ( err == JET_errNoCurrentRecord )
err = JET_errRecordNotFound;
return err;
}
ERR ErrBTDownFromDATA( FUCB *pfucb, KEY *pkey )
{
ERR err;
ERR errPos;
CSR *pcsr = PcsrCurrent( pfucb );
SSIB *pssib = &pfucb->ssib;
INT s;
INT ctagSon = 0;
BOOL fMoveToSeek = fFalse;
/* cache initial currency in case seek fails.
/**/
FUCBStore( pfucb );
/* set father currency to DATA root
/**/
pcsr->csrstat = csrstatOnCurNode;
/* read access page and check for valid time stamp
/**/
pcsr->pgno = PgnoRootOfPfucb( pfucb );
while ( !FReadAccessPage( pfucb, pcsr->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
pcsr->pgno = PgnoRootOfPfucb( pfucb );
}
pcsr->itagFather = ItagRootOfPfucb( pfucb );
NDGet( pfucb, pcsr->itagFather );
/* save current node as visible father
/**/
if ( FNDBackLink( *((pfucb)->ssib.line.pb) ) )
{
pfucb->sridFather = *(SRID UNALIGNED *)PbNDBackLink((pfucb)->ssib.line.pb);
}
else
{
pfucb->sridFather = SridOfPgnoItag( pcsr->pgno, pcsr->itagFather );
}
Assert( pfucb->sridFather != sridNull );
Assert( pfucb->sridFather != sridNullLink );
/* tree may be empty
/**/
if ( FNDNullSon( *pssib->line.pb ) )
{
err = JET_errRecordNotFound;
return err;
}
/* search down the tree from father.
/* set pssib->itag for invisible son traversal.
/**/
if ( !FNDVisibleSons( *pssib->line.pb ) )
{
/* seek through invisible sons.
/**/
do
{
/* get child page from intrisic page pointer
/* if son count is 1 or from page pointer node
/**/
if ( pcsr->itagFather != itagFOP && CbNDSon( pssib->line.pb ) == 1 )
{
/* If non-FDP page, SonTable of Intrinsic son FOP must be four bytes
/**/
AssertNDIntrinsicSon( pssib->line.pb, pssib->line.cb );
pcsr->pgno = PgnoNDOfPbSon( pssib->line.pb );
}
else
{
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
NDSeekSon( pfucb, pcsr, pkey, fDIRReplace );
pcsr->pgno = *(PGNO UNALIGNED *)PbNDData( pssib->line.pb );
}
/* get child page father node
/**/
Call( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
NDGet( pfucb, itagFOP );
pcsr->itagFather = itagFOP;
}
while ( !FNDVisibleSons( *pssib->line.pb ) );
}
/* down to visible son
/**/
if ( FNDSon( *pssib->line.pb ) )
{
ctagSon = CbNDSon( pssib->line.pb );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
NDSeekSon( pfucb, pcsr, pkey, fDIRReplace );
}
else
{
DIB dibT;
/* must move to seek
/**/
fMoveToSeek = fTrue;
/* If we land on an empty page and there are no next previous
/* nodes. What if the tree is empty. We must first reverse
/* direction and if no node is found then return an empty tree
/* error code. The empty tree error code should be the same
/* regardless of the size of the tree.
/**/
dibT.fFlags = fDIRNull;
err = ErrBTNextPrev( pfucb, pcsr, fTrue, &dibT );
if ( err == JET_errNoCurrentRecord )
Call( ErrBTNextPrev( pfucb, pcsr, fFalse, &dibT ) );
/* determine number of sons in FOP for this page
/**/
NDGet( pfucb, itagFOP );
ctagSon = CbNDSon( pssib->line.pb );
/* recache son node.
/**/
NDGet( pfucb, pcsr->itag );
}
/* we have landed on a visible node
/**/
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
if ( s == 0 )
errPos = JET_errSuccess;
else
{
if ( s < 0 )
errPos = wrnNDFoundLess;
else
errPos = wrnNDFoundGreater;
/* if on last node in page and found less, or if landed on
/* first node in page and found greater, move next or previous
/* to look for node with search key. These anomalies can
/* occur during update seek normally, and during read seek
/* when partial split pages are encountered.
/**/
Assert( ( ctagSon == 0 && pcsr->ibSon == 0 ) ||
pcsr->ibSon < ctagSon );
Assert( errPos == wrnNDFoundGreater &&
pcsr->ibSon == 0 ||
ctagSon == 0 ||
pcsr->ibSon <= ( ctagSon - 1 ) );
if ( fMoveToSeek ||
( errPos == wrnNDFoundLess && pcsr->ibSon == ( ctagSon - 1 ) ) ||
( errPos == wrnNDFoundGreater && pcsr->ibSon == 0 ) )
{
DIB dibT;
dibT.fFlags = fDIRNull;
dibT.pkey = pkey;
Call( ErrBTIMoveToSeek( pfucb, &dibT, errPos == wrnNDFoundLess ) );
errPos = err;
}
}
if ( !FBTThere( pfucb ) )
{
DIB dibT;
dibT.fFlags = fDIRNull;
/* if current node is not there for us then move to next node.
/* if no next node then move to previous node.
/* if no previous node then return error.
/**/
err = ErrBTNextPrev( pfucb, pcsr, fTrue, &dibT );
if ( err < 0 && err != JET_errNoCurrentRecord )
goto HandleError;
if ( err == JET_errNoCurrentRecord )
{
Call( ErrBTNextPrev( pfucb, pcsr, fFalse, &dibT ) );
}
/* preferentially land on lesser key value node
/**/
if ( CmpStKey( StNDKey( pssib->line.pb ), pkey ) > 0 )
{
err = ErrBTNextPrev( pfucb, pcsr, fFalse, &dibT );
if ( err == JET_errNoCurrentRecord )
{
/* cannot assume will find node since all nodes
/* may not be there for this session.
/**/
Call( ErrBTNextPrev( pfucb, pcsr, fTrue, &dibT ) );
}
}
/* reset errPos for new node location
/**/
s = CmpStKey( StNDKey( pssib->line.pb ), pkey );
if ( s > 0 )
errPos = wrnNDFoundGreater;
else if ( s < 0 )
errPos = wrnNDFoundLess;
Assert( s != 0 || errPos == JET_errSuccess );
Assert( err != JET_errKeyBoundary && err != JET_errPageBoundary );
if ( err == JET_errNoCurrentRecord )
{
DIB dibT;
dibT.fFlags = fDIRNull;
/* move previous
/**/
Call( ErrBTNextPrev( pfucb, pcsr, fFalse, &dibT ) );
errPos = wrnNDFoundLess;
}
else if ( err < 0 )
goto HandleError;
}
Assert( errPos >= 0 );
return errPos;
HandleError:
FUCBRestore( pfucb );
if ( err == JET_errNoCurrentRecord )
err = JET_errRecordNotFound;
return err;
}
//+private------------------------------------------------------------------------
// ErrBTNextPrev
// ===========================================================================
// ERR ErrBTNextPrev( FUCB *pfucb, CSR *pcsr INT fNext, const DIB *pdib )
//
// Given pcsr may be to any CSR in FUCB stack. We may be moving on
// non-current CSR when updating CSR stack for split.
//
// RETURNS JET_errSuccess
// JET_errNoCurrentRecord
// JET_errPageBoundary
// JET_errKeyBoundary
// error from called routine
//----------------------------------------------------------------------------
extern LONG lPageReadAheadMax;
ERR ErrBTNextPrev( FUCB *pfucb, CSR *pcsr, INT fNext, DIB *pdib )
{
ERR err;
PN pnNext;
SSIB *pssib = &pfucb->ssib;
PGNO pgnoSource;
PGNO pgnoT;
ERR wrn = 0;
ULONG ulPageReadAheadMax;
/* make current page accessible
/**/
if ( !FReadAccessPage( pfucb, pcsr->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
}
/* get father node
/**/
Start:
NDGet( pfucb, pcsr->itagFather );
pcsr->ibSon += ( fNext ? 1 : -1 );
err = ErrNDMoveSon( pfucb, pcsr );
if ( err < 0 )
{
Assert( err == errNDOutSonRange );
/* if tree interior to page, then there is no page to move
/* to and return end code.
/**/
if ( pcsr->itagFather != itagFOP )
{
pcsr->csrstat = fNext ? csrstatAfterCurNode : csrstatBeforeCurNode;
return JET_errNoCurrentRecord;
}
#ifdef INPAGE
/* do not move to next page if fDIRInPage set
/**/
if ( pdib->fFlags & fDIRInPage )
{
pcsr->ibSon -= ( fNext ? 1 : -1 );
pcsr->csrstat = fNext ? csrstatAfterCurNode : csrstatBeforeCurNode;
return JET_errPageBoundary;
}
#endif
pgnoSource = pcsr->pgno;
AssertNDGet( pfucb, PcsrCurrent( pfucb )->itagFather );
if ( FNDSon( *pssib->line.pb ) )
{
/* store bookmark for current node
/**/
NDGet( pfucb, pcsr->itag );
NDGetBookmarkFromCSR( pfucb, pcsr, &pfucb->bmRefresh );
}
else
{
/* store currency for refresh, when cursor
/* is on page with no sons.
/**/
pfucb->bmRefresh = SridOfPgnoItag( pcsr->pgno, itagFOP );
}
/* move to next or previous page until node found
/**/
forever
{
PGNO pgnoBeforeMoveNext = pcsr->pgno;
/* there may not be a next page
/**/
LFromThreeBytes( pgnoT, *(THREEBYTES *)PbPMGetChunk( pssib, fNext ? ibPgnoNextPage : ibPgnoPrevPage ) );
if ( pgnoT == pgnoNull )
{
pcsr->csrstat = fNext ? csrstatAfterLast : csrstatBeforeFirst;
return JET_errNoCurrentRecord;
}
/* if parent CSR points to invisible node, then correct to next page.
/* Check all node flag, since it is always set on movement for
/* update, and when moving not for update, parent CSR may not be CSR
/* of parent invisible node.
/**/
if ( FFUCBFull( pfucb ) )
{
CSR *pcsrT = pcsr->pcsrPath;
DIB dibT = { 0, NULL, fDIRAllNode };
Assert( pcsrT != pcsrNil );
/* go to parent node, and
/* if sons are invisible, then increment son count
/* by cpageTraversed.
/**/
CallR( ErrSTReadAccessPage( pfucb, pcsrT->pgno ) );
Assert( FReadAccessPage( pfucb, pcsrT->pgno ) );
NDGet( pfucb, pcsrT->itagFather );
if ( FNDInvisibleSons( *pssib->line.pb ) )
{
err = ErrBTNextPrev( pfucb, pcsrT, fNext, &dibT );
Assert( err != JET_errNoCurrentRecord );
CallR( err );
}
}
/* access new page
/**/
pcsr->pgno = pgnoT;
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
/* if destination page was split, such that data may have
/* been erroneously skipped, goto bookmark of last valid
/* postion and move again.
/**/
if ( fNext )
{
PgnoPrevFromPage( pssib, &pgnoT );
}
else
{
PgnoNextFromPage( pssib, &pgnoT );
}
if ( pgnoBeforeMoveNext != pgnoT )
{
BFSleep( cmsecWaitGeneric );
Call( ErrBTGotoBookmark( pfucb, pfucb->bmRefresh ) );
continue;
}
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
/* read ahead
/**/
// ulPageReadAheadMax = (ULONG) lPageReadAheadMax;
ulPageReadAheadMax = 1;
if ( fNext )
{
Assert( pfucb->cpn <= ulPageReadAheadMax );
if ( pfucb->cpn == 0 || --pfucb->cpn < ulPageReadAheadMax / 2 )
{
PgnoNextFromPage( pssib, &pnNext );
if ( pnNext != pnNull )
{
INT ipn = 0;
pnNext |= ((LONG)pfucb->dbid)<<24;
pnNext += pfucb->cpn;
pfucb->cpn = ulPageReadAheadMax;
/* lock the contents to make sure the pfucb->lineData
/* are effective after ReadAsyn.
/**/
BFPin( pfucb->ssib.pbf );
// BFSetReadLatch( pfucb->ssib.pbf, pfucb->ppib );
BFReadAsync( pnNext, ulPageReadAheadMax );
// BFResetReadLatch( pfucb->ssib.pbf, pfucb->ppib );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
BFUnpin( pfucb->ssib.pbf );
}
else
{
/* reset read-ahead counter when reach end of index.
/**/
pfucb->cpn = 0;
}
}
}
else
{
Assert( pfucb->cpn <= ulPageReadAheadMax );
if ( pfucb->cpn == 0 || --pfucb->cpn < ulPageReadAheadMax / 2 )
{
PgnoPrevFromPage( pssib, &pnNext );
if ( pnNext != pnNull )
{
/* cannot read-ahead off begining of database.
/**/
if ( pnNext > pfucb->cpn )
{
pnNext |= ((LONG)pfucb->dbid)<<24;
pnNext -= pfucb->cpn;
pfucb->cpn = ulPageReadAheadMax;
/* lock the contents to make sure the
/* pfucb->lineData are effective after ReadAsyn.
/**/
BFPin( pfucb->ssib.pbf );
// BFSetReadLatch( pfucb->ssib.pbf, pfucb->ppib );
BFReadAsync( pnNext - ( ulPageReadAheadMax - 1 ), ulPageReadAheadMax );
// BFResetReadLatch( pfucb->ssib.pbf, pfucb->ppib );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
BFUnpin( pfucb->ssib.pbf );
}
}
else
{
/* reset read-ahead counter when reach end of index.
/**/
pfucb->cpn = 0;
}
}
}
/* check read access again since buffer may
/* be wait latched. Note that it has been found
/* wait latched as a result of loss of critJet.
/**/
if ( !FReadAccessPage( pfucb, pcsr->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
}
/* get father node
/**/
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
pcsr->itagFather = itagFOP;
NDGet( pfucb, pcsr->itagFather );
/* if moving to next/prev son, then stop if found node.
/**/
if ( FNDSon( *pssib->line.pb ) )
{
if ( fNext )
NDMoveFirstSon( pfucb, pcsr );
else
NDMoveLastSon( pfucb, pcsr );
break;
}
else
{
/* set ibSon for insertion
/**/
pcsr->ibSon = 0;
/* if page is write latched by this cursor via
/* split then return wrnDIREmptyPage as insertion point
/**/
if ( pfucb->ssib.pbf->cWriteLatch > 0 &&
pfucb->ssib.pbf->ppibWriteLatch == pfucb->ppib )
wrn = wrnDIREmptyPage;
if ( pdib->fFlags & fDIRAllPage )
{
err = JET_errSuccess;
goto HandleError;
}
}
/* update pgnoSource to new source page.
/**/
pgnoSource = pcsr->pgno;
}
}
/* get current node
/**/
NDGet( pfucb, pcsr->itag );
/* move again if fDIRNeighborKey set and next node has same key
/**/
if ( pdib->fFlags & fDIRNeighborKey )
{
if ( CmpStKey( StNDKey( pssib->line.pb ), pdib->pkey ) == 0 )
goto Start;
}
if ( !( pdib->fFlags & fDIRAllNode ) )
{
if ( !FBTThere( pfucb ) )
{
if ( ( pdib->fFlags & fDIRPotentialNode ) != 0 )
{
VS vs;
BOOL fDelete = FNDDeleted( *pssib->line.pb );
SRID srid;
NDGetBookmark( pfucb, &srid );
vs = VsVERCheck( pfucb, srid );
if ( !( FVERPotThere( vs, fDelete ) ) )
{
goto Start;
}
}
else
goto Start;
}
}
pcsr->csrstat = csrstatOnCurNode;
err = JET_errSuccess;
HandleError:
/* return empty page warning.
/**/
if ( err == JET_errSuccess )
err = wrn;
return err;
}
ERR ErrBTSeekForUpdate( FUCB *pfucb, KEY *pkey, PGNO pgno, INT itag, INT fFlags )
{
ERR err;
CSR **ppcsr = &PcsrCurrent( pfucb );
CSR *pcsrRoot = *ppcsr;
SSIB *pssib = &pfucb->ssib;
ERR errPos = JET_errSuccess;
Assert( ( fFlags & fDIRReplace ) || pgno == pgnoNull );
/* search down the tree from the father
/**/
Call( ErrBTIMoveToFather( pfucb ) );
if ( FNDNullSon( *pssib->line.pb ) )
{
(*ppcsr)->ibSon = 0;
errPos = wrnNDFoundGreater;
goto Done;
}
while ( !FNDVisibleSons(*pssib->line.pb) )
{
PGNO pgno;
if ( (*ppcsr)->itagFather != itagFOP && CbNDSon( pssib->line.pb ) == 1 )
{
/* if non-FDP page, SonTable of Intrinsic son FOP must be four bytes
/**/
(*ppcsr)->ibSon = 0;
(*ppcsr)->itag = itagNil;
(*ppcsr)->csrstat = csrstatOnCurNode;
AssertNDIntrinsicSon( pssib->line.pb, pssib->line.cb );
pgno = PgnoNDOfPbSon( pssib->line.pb );
Assert( (pgno & 0xff000000) == 0 );
}
else
{
Assert( FReadAccessPage( pfucb, (*ppcsr)->pgno ) );
NDSeekSon( pfucb, *ppcsr, pkey, fFlags );
(*ppcsr)->csrstat = csrstatOnCurNode;
pgno = *(PGNO UNALIGNED *)PbNDData( pssib->line.pb );
Assert( (pgno & 0xff000000) == 0 );
}
/* only preserve invisible CSR stack for splits
/**/
if ( FFUCBFull( pfucb ) )
{
CSRSetInvisible( *ppcsr );
Call( ErrFUCBNewCSR( pfucb ) );
}
(*ppcsr)->pgno = pgno;
Call( ErrSTReadAccessPage( pfucb, (*ppcsr)->pgno ) );
Assert( FReadAccessPage( pfucb, (*ppcsr)->pgno ) );
(*ppcsr)->itagFather = itagFOP;
NDGet( pfucb, (*ppcsr)->itagFather );
}
/* seek to son or move to next son if no nodes on this page.
/**/
if ( FNDSon( *pssib->line.pb ) )
{
Assert( FReadAccessPage( pfucb, (*ppcsr)->pgno ) );
NDSeekSon( pfucb, *ppcsr, pkey, fFlags );
(*ppcsr)->csrstat = csrstatOnCurNode;
/* no current record indicates no sons so must ensure
/* not this error value here.
/**/
Assert( err != JET_errNoCurrentRecord );
}
else
{
DIB dib;
dib.fFlags = fDIRAllNode;
err = ErrBTNextPrev( pfucb, PcsrCurrent( pfucb ), fTrue, &dib );
if ( err == JET_errNoCurrentRecord )
{
err = ErrBTNextPrev( pfucb, PcsrCurrent( pfucb ), fFalse, &dib );
Assert( err >= JET_errSuccess || PcsrCurrent( pfucb )->ibSon == 0 );
}
}
/* if no leaf sons then ibSon must be 0
/**/
Assert( err != JET_errNoCurrentRecord || PcsrCurrent( pfucb )->ibSon == 0 );
/* now we must be on a node, but it may not be the node to replace
/* or the correct location to insert. If we are replacing a node
/* and we are not on the correct pgno:itag, then move to node to
/* replace. If we are inserting, then move to correct insert location.
/**/
if ( err != JET_errNoCurrentRecord )
{
if ( fFlags & fDIRReplace )
{
if ( ( (*ppcsr)->pgno != pgno || (*ppcsr)->itag != itag ) )
{
Call( ErrBTIMoveToReplace( pfucb, pkey, pgno, itag ) );
Assert( (*ppcsr)->itag == itag && (*ppcsr)->pgno == pgno );
}
errPos = JET_errSuccess;
(*ppcsr)->csrstat = csrstatOnCurNode;
}
else
{
Call( ErrBTIMoveToInsert( pfucb, pkey, fFlags ) );
errPos = err;
(*ppcsr)->csrstat = csrstatBeforeCurNode;
}
}
else
{
/* if we are attempting a replace, cursor must get current record
/**/
Assert( !( fFlags & fDIRReplace ) );
}
Done:
FUCBResetStore( pfucb );
return errPos;
HandleError:
FUCBFreePath( ppcsr, pcsrRoot );
FUCBRestore( pfucb );
return err;
}
/* Caller seeks to insert location, prior to calling ErrBTInsert.
/* If sufficient page space is available for insertion
/* then insert takes place. Otherwise, split page and return error
/* code. Caller may reseek in order to avoid duplicate keys, merge
/* into existing item, etc..
/**/
ERR ErrBTInsert( FUCB *pfucb, INT fHeader, KEY *pkey, LINE *pline, INT fFlags )
{
ERR err;
SSIB *pssib = &pfucb->ssib;
CSR **ppcsr = &PcsrCurrent( pfucb );
INT cbReq;
BOOL fAppendNextPage;
/* insert a new son into the page and insert the son entry
/* to the father node located by the currency
/**/
cbReq = cbNullKeyData + CbKey( pkey ) + CbLine( pline );
if ( ( fAppendNextPage = FBTAppendPage( pfucb, *ppcsr, cbReq, 0, CbFreeDensity( pfucb ) ) ) || FBTSplit( pssib, cbReq, 1 ) )
{
Call( ErrBTSplit( pfucb, 0, cbReq, pkey, fFlags ) );
err = errDIRNotSynchronous;
goto HandleError;
}
/* must not give up critical section during insert, since
/* other thread could also insert node with same key.
/**/
Assert( FWriteAccessPage( pfucb, (*ppcsr)->pgno ) );
/* add visible son flag to node header
/**/
NDSetVisibleSons( fHeader );
if ( fFlags & fDIRVersion )
NDSetVersion( fHeader);
Call( ErrNDInsertNode( pfucb, pkey, pline, fHeader ) );
PcsrCurrent( pfucb )->csrstat = csrstatOnCurNode;
HandleError:
return err;
}
ERR ErrBTReplace( FUCB *pfucb, LINE *pline, INT fFlags )
{
ERR err;
SSIB *pssib;
INT cbNode;
INT cbReq;
/* replace data
/**/
Assert( FWriteAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
AssertNDGetNode( pfucb, PcsrCurrent( pfucb )->itag );
err = ErrNDReplaceNodeData( pfucb, pline, fFlags );
/* new data could not fit on page so split page
/**/
if ( err == errPMOutOfPageSpace )
{
INT cbReserved;
AssertNDGet( pfucb, PcsrCurrent( pfucb )->itag );
pssib = &pfucb->ssib;
if ( FNDVersion( *pfucb->ssib.line.pb ) )
{
// UNDONE: change CbVERGetNodeReserved to take itag from CSR
pssib->itag = PcsrCurrent( pfucb )->itag;
cbReserved = CbVERGetNodeReserve( pfucb, PcsrCurrent( pfucb )->bm );
if ( cbReserved < 0 )
cbReserved = 0;
}
else
{
cbReserved = 0;
}
cbNode = pfucb->ssib.line.cb;
cbReq = pline->cb - CbNDData( pssib->line.pb, pssib->line.cb );
Assert( cbReserved >= 0 && cbReq - cbReserved > 0 );
cbReq -= cbReserved;
Assert( cbReq > 0 );
Assert( pfucb->pbfEmpty == pbfNil );
Call( ErrBTSplit( pfucb, cbNode, cbReq, NULL, fFlags | fDIRDuplicate | fDIRReplace ) );
Assert( pfucb->pbfEmpty == pbfNil );
err = errDIRNotSynchronous;
}
HandleError:
return err;
}
ERR ErrBTDelete( FUCB *pfucb, INT fFlags )
{
ERR err;
/* write access current node
/**/
if ( !( FWriteAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) ) )
{
Call( ErrSTWriteAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
}
Call( ErrNDFlagDeleteNode( pfucb, fFlags ) );
Assert( err == JET_errSuccess );
HandleError:
return err;
}
/* Gets the invisible csrPath to this page
/* using BTSeekForUpdate from sridFather
/**/
ERR ErrBTGetInvisiblePagePtr( FUCB *pfucb, SRID sridFather )
{
ERR err = JET_errSuccess;
SSIB *pssib = &pfucb->ssib;
CSR **ppcsr = &PcsrCurrent( pfucb );
/* store currency for split path construction
/**/
BYTE rgb[JET_cbKeyMost];
KEY key;
PGNO pgno;
INT itag;
/* cache pgno, itag and key of current node
/* for subsequent seek for update
/**/
pgno = (*ppcsr)->pgno;
itag = (*ppcsr)->itag;
key.pb = rgb;
NDGet( pfucb, (*ppcsr)->itag );
key.cb = CbNDKey( pssib->line.pb );
Assert( sizeof(rgb) >= key.cb );
memcpy( rgb, PbNDKey( pssib->line.pb ), key.cb );
/* move to visible father and seek for update.
/**/
FUCBStore( pfucb );
Call( ErrBTGotoBookmark( pfucb, sridFather ) );
if ( !FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
Assert( FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
}
/* if sridFather is of node in same page to free then
/* return error.
/**/
if ( PcsrCurrent( pfucb )->pgno == pgno )
return errDIRInPageFather;
FUCBSetFull( pfucb );
err = ErrBTSeekForUpdate( pfucb, &key, pgno, itag, fDIRReplace );
FUCBResetFull( pfucb );
Call( err );
Assert( err == JET_errSuccess );
Assert( (*ppcsr)->pgno == pgno && (*ppcsr)->itag == itag );
Assert( PcsrCurrent( pfucb )->pcsrPath != pcsrNil );
FUCBResetStore( pfucb );
return err;
HandleError:
FUCBFreePath( &PcsrCurrent( pfucb )->pcsrPath, pcsrNil );
FUCBRestore( pfucb ) ;
return err;
}
#ifdef DEBUG
/* checks the invisible csrPath to this page
/* using BTSeekForUpdate from sridFather
/**/
ERR ErrBTCheckInvisiblePagePtr( FUCB *pfucb, SRID sridFather )
{
ERR err = JET_errSuccess;
SSIB *pssib = &pfucb->ssib;
CSR **ppcsr = &PcsrCurrent( pfucb );
/* store currency for split path construction
/**/
BYTE rgb[JET_cbKeyMost];
KEY key;
PGNO pgno;
INT itag;
/* cache pgno, itag and key of current node
/* for subsequent seek for update
/**/
pgno = (*ppcsr)->pgno;
itag = (*ppcsr)->itag;
key.pb = rgb;
NDGet( pfucb, (*ppcsr)->itag );
key.cb = CbNDKey( pssib->line.pb );
Assert( sizeof(rgb) >= key.cb );
memcpy( rgb, PbNDKey( pssib->line.pb ), key.cb );
/* move to visible father and seek for update.
/**/
FUCBStore( pfucb );
Call( ErrBTGotoBookmark( pfucb, sridFather ) );
if ( !FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
Assert( FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
}
err = ErrBTSeekForUpdate( pfucb, &key, pgno, itag, fDIRReplace );
Call( err );
Assert( err == JET_errSuccess );
Assert( (*ppcsr)->pgno == pgno && (*ppcsr)->itag == itag );
Assert( PcsrCurrent( pfucb )->pcsrPath != pcsrNil );
FUCBResetStore( pfucb );
return err;
HandleError:
FUCBFreePath( &PcsrCurrent( pfucb )->pcsrPath, pcsrNil );
FUCBRestore( pfucb ) ;
return err;
}
#endif
ERR ErrBTGetPosition( FUCB *pfucb, ULONG *pulLT, ULONG *pulTotal )
{
ERR err;
CSR *pcsrRoot = PcsrCurrent( pfucb );
CSR *pcsrT;
SSIB *pssib = &pfucb->ssib;
BYTE rgb[JET_cbKeyMost];
KEY key;
ULONG ulTotal;
ULONG ulLT;
PGNO pgno = PcsrCurrent( pfucb )->pgno;
INT itag = PcsrCurrent( pfucb )->itag;
/* ErrBTGetPosition returns the position of the current leaf node
/* with respect to its siblings in the current tree. The position
/* is returned in the form of an estimated total tree leaf nodes,
/* at the leaf level, and an estimated number
/* of nodes at the same level, occurring previous in key order to
/* the current node.
/*
/* create full path from parent to node. Calculate estimates
/* from path page information. Free invisable path.
/**/
/* this function only supports index leaf nodes
/**/
Assert( FFUCBIndex( pfucb ) );
/* cache key of current node
/**/
AssertNDGet( pfucb, PcsrCurrent( pfucb )->itag );
key.cb = CbNDKey( pssib->line.pb );
memcpy( rgb, PbNDKey( pssib->line.pb ), key.cb );
key.pb = rgb;
CallR( ErrFUCBNewCSR( pfucb ) );
/* goto data root
/**/
PcsrCurrent( pfucb )->bm = pfucb->u.pfcb->bmRoot;
PcsrCurrent( pfucb )->itagFather = itagNull;
PcsrCurrent( pfucb )->pgno = PgnoRootOfPfucb( pfucb );
while( !FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
Assert( FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
PcsrCurrent( pfucb )->pgno = PgnoRootOfPfucb( pfucb );
}
PcsrCurrent( pfucb )->itag = ItagRootOfPfucb( pfucb );
/* invisible path is NOT MUTEX guarded, and may be invalid. However,
/* since it is only being read for position calculation and discarded
/* immediately after, it need not be valid.
/**/
FUCBSetFull( pfucb );
Assert( FReadAccessPage( pfucb, PcsrCurrent( pfucb )->pgno ) );
Call( ErrBTSeekForUpdate( pfucb, &key, pgno, itag, fDIRDuplicate | fDIRReplace ) );
Assert( PcsrCurrent( pfucb )->csrstat == csrstatOnCurNode );
Assert( PcsrCurrent( pfucb )->pgno == pgno &&
PcsrCurrent( pfucb )->itag == itag );
/* now follow path from root down to current node, to estimate
/* total and number nodes less than current node.
/**/
ulTotal = 1;
ulLT = 0;
for ( pcsrT = PcsrCurrent( pfucb ); pcsrT->pcsrPath != pcsrRoot; pcsrT = pcsrT->pcsrPath )
{
Call( ErrSTReadAccessPage( pfucb, pcsrT->pgno ) );
Assert( FReadAccessPage( pfucb, pcsrT->pgno ) );
NDGet( pfucb, pcsrT->itagFather );
/* calculate fractional position in B-tree
/**/
ulLT += pcsrT->ibSon * ulTotal;
ulTotal *= CbNDSon( pssib->line.pb );
}
/* return results
/**/
*pulLT = ulLT;
*pulTotal = ulTotal;
HandleError:
FUCBFreePath( &pfucb->pcsr, pcsrRoot );
FUCBResetFull( pfucb );
return err;
}
LOCAL INT IbsonBTFrac( FUCB *pfucb, CSR *pcsr, DIB *pdib )
{
SSIB *pssib = &pfucb->ssib;
INT ibSon;
INT cbSon;
FRAC *pfrac = (FRAC *)pdib->pkey;
ULONG ulT;
Assert( pdib->pos == posFrac );
NDGet( pfucb, pcsr->itagFather );
cbSon = CbNDSon( pssib->line.pb );
/* effect fractional in page positioning such that overflow and
/* underflow are avoided.
/**/
if ( pfrac->ulTotal / cbSonMax == 0 )
{
ibSon = ( ( pfrac->ulLT * cbSon ) / pfrac->ulTotal );
}
else
{
ibSon = ( cbSon * ( pfrac->ulLT / ( pfrac->ulTotal / cbSonMax ) ) ) / cbSonMax;
}
if ( ibSon >= cbSon )
ibSon = cbSon - 1;
/* preseve fractional information by avoiding underflow
/**/
if ( pfrac->ulTotal / cbSon == 0 )
{
pfrac->ulTotal *= cbSonMax;
pfrac->ulLT *= cbSonMax;
}
/* prepare fraction for next lower B-tree level
/**/
pfrac->ulTotal /= cbSon;
Assert( pfrac->ulTotal > 0 );
ulT = ibSon * pfrac->ulTotal;
if ( ulT > pfrac->ulLT )
pfrac->ulLT = 0;
else
pfrac->ulLT -= ulT;
return ibSon;
}
ERR ErrBTGotoBookmark( FUCB *pfucb, SRID srid )
{
ERR err;
CSR *pcsr = PcsrCurrent( pfucb );
SSIB *pssib = &pfucb->ssib;
SRID sridT;
PGNO pgno;
INT itag = itagFOP;
INT ibSon;
ULONG crepeat = 0;
Start:
crepeat++;
Assert( crepeat < 10 );
sridT = srid;
Assert( sridT != sridNull );
pcsr->pgno = PgnoOfSrid( sridT );
pcsr->itag = ItagOfSrid( sridT );
Assert( pcsr->pgno != pgnoNull );
Assert( pcsr->itag >= 0 && pcsr->itag < ctagMax );
if ( !FReadAccessPage( pfucb, pcsr->pgno ) )
{
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
}
if ( TsPMTagstatus( pfucb->ssib.pbf->ppage, pcsr->itag ) == tsVacant )
{
/* node has probably moved from under us -- retry
/**/
BFSleep( cmsecWaitGeneric );
goto Start;
}
else if ( TsPMTagstatus( pfucb->ssib.pbf->ppage, pcsr->itag ) == tsLink )
{
PMGetLink( &pfucb->ssib, pcsr->itag, &sridT );
pgno = PgnoOfSrid( sridT );
Assert( pgno != pgnoNull );
pcsr->pgno = pgno;
pcsr->itag = ItagOfSrid( sridT );
Assert( pcsr->itag > 0 && pcsr->itag < ctagMax );
CallR( ErrSTReadAccessPage( pfucb, pcsr->pgno ) );
Assert( FReadAccessPage( pfucb, pcsr->pgno ) );
if ( TsPMTagstatus( pfucb->ssib.pbf->ppage, pcsr->itag ) != tsLine )
{
/* might have been merged into adjacent page
/* go back to link and check
/**/
BFSleep( cmsecWaitGeneric );
goto Start;
}
/* get line and check if backlink is what we expected
/**/
NDGet( pfucb, PcsrCurrent( pfucb )->itag );
sridT = *(SRID UNALIGNED *)PbNDBackLink( pfucb->ssib.line.pb );
if ( sridT != srid && pcsr->itag != 0 )
{
BFSleep( cmsecWaitGeneric );
goto Start;
}
}
/* search all node son tables for tag of node.
/**/
Assert( pcsr == PcsrCurrent( pfucb ) );
if ( pcsr->itag == 0 )
{
/* this is for case where cursor is on FDP root or page with no
/* sons and stores page currency.
/**/
ibSon = 0;
}
else
{
NDGetItagFatherIbSon(
&itag,
&ibSon,
pssib->pbf->ppage,
pcsr->itag );
}
/* set itagFather and ibSon
/**/
pcsr->itagFather = itag;
pcsr->ibSon = ibSon;
/* get line -- UNDONE: optimize -- line may have already been got
/**/
NDGet( pfucb, PcsrCurrent( pfucb )->itag );
/* bookmark must be on node for this table
/**/
// UNDONE: cannot assert this since space manager cursors
// traverse domains, as do database cursors
// Assert( pfucb->u.pfcb->pgnoFDP == PgnoPMPgnoFDPOfPage( pfucb->ssib.pbf->ppage ) );
return JET_errSuccess;
}