#include "config.h" #include #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; }