// B-Tree Maintenance Routines // Copyright Xerox Corporation 1979, 1981, 1982 // BTreeRead.bcpl -- Routines for reading B-Trees // last edited May 9, 1982 5:25 PM by Taft get "BTree.decl" external [ MoveBlock Allocate Free DefaultArgs Zero Dvec ReadBTreePage // defined in OpenBTree WriteBTreePage LockBTreePtr UnlockBTreePtr ] external [ ReadRecLE // outgoing procedues MapTree PathRecLE BackUpOneRecord RepairOffsets ReadStackAndLockBTreePtr ReadPageAndLockBTreePtr ReadStack PopStack GetRecord ] // For the externally-called procedures (ReadRecLE, MapTree, UpdateRecord, // and DeleteKey), if caller supplies a PathStk and UseExistingPath // is true, then the caller asserts that the supplied PathStk was the result // of a previous call with the same Key. (Note that the B-Tree package is // responsible for determining whether or not the tree has changed since // the PathStk was computed, so the caller need not worry about that.) // In any event, if caller supplies a PathStk then it is filled in with // the path to the entry returned by ReadRecLE or the last entry enumerated // by MapTree; UpdateRecord and DeleteKey invalidate the supplied PathStk. let ReadRecLE(Tree, Key, CKRtn, dontCopy, PathStk, UseExistingPath; numargs na) = valof [ DefaultArgs(lv na, -2, Tree>>TREE.CompareKeyRtn, false, size PS/16, false) if PathStk eq size PS/16 then [ Dvec(ReadRecLE, lv PathStk); UseExistingPath = false ] PathRecLE(Tree, PathStk, UseExistingPath, Key, CKRtn) resultis GetRecord(Tree, PathStk, dontCopy) ] and MapTree(Tree, StartKey, Function, Param, CKRtn, dontCopy, PathStk, UseExistingPath; numargs na) = valof [ DefaultArgs(lv na, -3, 0, Tree>>TREE.CompareKeyRtn, false, size PS/16, false) if PathStk eq size PS/16 then [ Dvec(MapTree, lv PathStk); UseExistingPath = false ] let LeafStkTop = PathRecLE(Tree, PathStk, UseExistingPath, StartKey, CKRtn) if PathStk>>PS.PathStkTop eq 0 then // Key is less than any existing record in the tree. // Set PathStk to first record in leftmost leaf page. [ if LeafStkTop eq 0 then resultis true // empty tree PathStk>>PS.PathStkTop = LeafStkTop PathStk>>PS.PSE^LeafStkTop.LastOffset = Rec1Offset ] let SP = lv PathStk>>PS.PSE^(PathStk>>PS.PathStkTop) let PagePtr = 0 LockBTreePtr(Tree, lv PagePtr) [ // repeat let Offset = SP>>PSE.LastOffset if PagePtr eq 0 then PagePtr = ReadBTreePage(Tree, SP>>PSE.PageNo) test Offset ge Tree>>TREE.PageLength - PagePtr>>BTP.FreeWords ifso // Ran off end of page. Pop up a level and do the next // entry in the father page. [ PagePtr = 0 PathStk>>PS.PathStkTop = PathStk>>PS.PathStkTop-1 if PathStk>>PS.PathStkTop le 0 then // Ran off end of tree. PathStk is no longer valid. [ UnlockBTreePtr(Tree, lv PagePtr) PathStk>>PS.Version = Tree>>TREE.Version-1 resultis true ] SP = SP - size PSE/16 ] ifnot // Do next entry in current page. [ let NxtPgNo = (PagePtr+Offset)>>BTE.GrPtr let RecPtr = lv (PagePtr+Offset)>>BTE.Record let RecLen = (Tree>>TREE.LengthRtn)(RecPtr) SP>>PSE.Offset = Offset+(offset BTE.Record/16)+RecLen unless dontCopy do [ let CopyRec = Allocate(Tree>>TREE.Zone, RecLen) MoveBlock(CopyRec, RecPtr, RecLen) RecPtr = CopyRec PagePtr = 0 // unlock the B-Tree page ] unless Function(RecPtr, Param, PathStk) do [ UnlockBTreePtr(Tree, lv PagePtr) resultis false ] // If we are in a non-leaf page, find the leftmost // right descendant of the entry we just did. while NxtPgNo ne Empty do [ PathStk>>PS.PathStkTop = PathStk>>PS.PathStkTop+1 SP = SP + size PSE/16 SP>>PSE.PageNo = NxtPgNo SP>>PSE.NextToLastOffset = Empty SP>>PSE.LastOffset = Rec0Offset SP>>PSE.Offset = Rec1Offset PagePtr = ReadBTreePage(Tree, NxtPgNo) NxtPgNo = PagePtr>>BTP.MinPtr ] ] SP>>PSE.NextToLastOffset = SP>>PSE.LastOffset SP>>PSE.LastOffset = SP>>PSE.Offset ] repeat ] and PathRecLE(Tree, PathStk, UseExistingPath, Key, CKR) = valof // Computes a PathStk for the supplied Key such that at each level // PSE.LastOffset refers to an entry less than or equal to Key and // PSE.Offset refers to an entry strictly greater than Key, if any // such entries exist. Sets PathStk>>PS.PathStkTop to the deepest level // in which PSE.LastOffset refers to a real entry (i.e., not Rec0Offset), // unless no such entry exists in the entire tree, in which case sets // PathStkTop to zero. Returns the depth of the tree, i.e., the // PathStkTop for the leaf page (which is either the page containing // the LE entry or its leftmost right descendant page). [ if UseExistingPath & PathStk>>PS.Version eq Tree>>TREE.Version then // Supplied PathStk still valid; no need to search tree. [ let PathStkTop = PathStk>>PS.PathStkTop while PathStk>>PS.PSE^PathStkTop.LeastSon ne Empty do PathStkTop = PathStkTop+1 resultis PathStkTop ] Zero(PathStk, (offset PS.PSE^1)/16) // overhead and dummy PSE PathStk>>PS.Version = Tree>>TREE.Version let offsetTable = Allocate(Tree>>TREE.Zone, maxRecordsPerPage+3) MoveBlock(offsetTable, table [ Empty; Rec0Offset; Rec1Offset ], 3) let PathStkTop = 0 let SP = lv PathStk>>PS.PSE^0 let CurPtr = 0 LockBTreePtr(Tree, lv CurPtr) let CurPage = Tree>>TREE.RootPage while CurPage ne Empty do [ PathStkTop = PathStkTop+1 SP = SP + size PSE/16 SP>>PSE.PageNo = CurPage CurPtr = 0 CurPtr = ReadBTreePage(Tree, CurPage) let firstGr = BinSearchPage(Tree, CurPtr, offsetTable, CKR, Key) SP>>PSE.Offset = offsetTable!firstGr SP>>PSE.LastOffset = offsetTable!(firstGr-1) SP>>PSE.NextToLastOffset = offsetTable!(firstGr-2) SP>>PSE.LeastSon = CurPtr>>BTP.MinPtr if firstGr gr 2 then PathStk>>PS.PathStkTop = PathStkTop SP>>PSE.ESLFront = 0 SP>>PSE.ESLRear = 0 CurPage = (CurPtr+(SP>>PSE.LastOffset))>>BTE.GrPtr ] UnlockBTreePtr(Tree, lv CurPtr) Free(Tree>>TREE.Zone, offsetTable) resultis PathStkTop ] and BinSearchPage(Tree, curPtr, offsetTable, CKR, Key) = valof // Returns an index r such that offsetTable!r is the offset of // the first record strictly greater than Key, and fills in // offsetTable!2 through offsetTable!r with the offsets of // all records up to and including that one. [ let LR = Tree>>TREE.LengthRtn let firstBadOffset = Tree>>TREE.PageLength-curPtr>>BTP.FreeWords let midOffset = firstBadOffset rshift 1 let curOffset = Rec1Offset let l = 2 let r = 2 // Scan records linearly and build offsetTable until half the in-use // portion of the B-Tree page has been examined. Then do one // key comparison to determine which half of the page to direct our // attention to, and build the rest of offsetTable only if necessary. // On the average this saves 25% of the work of building offsetTables, // and costs no extra comparisons since the first comparison would // examine a middle record anyway (not necessarily the same one). [ // repeat if curOffset ge midOffset then [ if curOffset ge firstBadOffset % CKR(Key, lv (curPtr+curOffset)>>BTE.Record) ls 0 then break midOffset = firstBadOffset l = r ] curOffset = curOffset + offset BTE.Record/16 curOffset = curOffset + LR(curPtr+curOffset) r = r + 1 offsetTable!r = curOffset ] repeat // At this point, offsetTable!r is the offset of the first non-record, // which we assume to have an infinite key (that we shall never test). // We shall leave r pointing at the offset of the first record whose // key is greater than Key. while l ls r do [ let m = (l+r) rshift 1 switchon CKR(Key, lv (curPtr+offsetTable!m)>>BTE.Record) into [ case 1: l = m+1; loop // Key > record case 0: resultis m+1 // Key = record case -1: r = m; loop // Key < record ] ] resultis r ] and GetRecord(Tree, PathStk, dontCopy) = valof [ let PathStkTop = PathStk>>PS.PathStkTop if PathStkTop eq 0 then resultis Empty let SP = lv (PathStk>>PS.PSE^PathStkTop) let PagePtr = 0 ReadStackAndLockBTreePtr(Tree, PathStk, lv PagePtr) let BestRecPtr = lv ((PagePtr+SP>>PSE.LastOffset)>> BTE.Record) unless dontCopy do [ let BestRecLen = (Tree>>TREE.LengthRtn)(BestRecPtr) let BestRec = Allocate(Tree>>TREE.Zone, BestRecLen) MoveBlock(BestRec, BestRecPtr, BestRecLen) BestRecPtr = BestRec ] UnlockBTreePtr(Tree, lv PagePtr) resultis BestRecPtr // recipient responsible for locking ] and BackUpOneRecord(Tree, PathStkEnt) be [ RepairOffsets(Tree, PathStkEnt) PathStkEnt>>PSE.Offset = PathStkEnt>>PSE.LastOffset RepairOffsets(Tree, PathStkEnt) ] and RepairOffsets(Tree, PathStkEnt) be [ let Ptr = nil ReadPageAndLockBTreePtr(Tree, PathStkEnt>>PSE.PageNo, lv Ptr) let Offset = PathStkEnt>>PSE.Offset let LastOffset = PathStkEnt>>PSE.LastOffset let NextToLastOffset = PathStkEnt>>PSE.NextToLastOffset unless CheckProgression(Tree, Ptr, LastOffset, Offset) & CheckProgression(Tree, Ptr, NextToLastOffset, LastOffset) do [ let newOffset = Rec1Offset LastOffset = Rec0Offset NextToLastOffset = Empty while newOffset ne Offset do [ NextToLastOffset = LastOffset LastOffset = newOffset newOffset = newOffset+ (offset BTE.Record/16)+ (Tree>>TREE.LengthRtn)(lv ((Ptr+newOffset)>> BTE.Record)) ] PathStkEnt>>PSE.LastOffset = LastOffset PathStkEnt>>PSE.NextToLastOffset = NextToLastOffset ] UnlockBTreePtr(Tree, lv Ptr) ] and CheckProgression(Tree, Ptr, Offset1, Offset2) = selecton Offset2 into [ case Rec0Offset: Offset1 eq Empty case Rec1Offset: Offset1 eq Rec0Offset default: (Offset1 ge Offset2)? false, (Offset1+(offset BTE.Record/16)+ (Tree>>TREE.LengthRtn)( lv (Ptr+Offset1)>>BTE.Record)) eq Offset2 ] and ReadStack(Tree, PathStk) = ReadBTreePage(Tree, PathStk>>PS.PSE^(PathStk>>PS.PathStkTop).PageNo) and ReadPageAndLockBTreePtr(Tree, pageNo, pPtr) be [ @pPtr = ReadBTreePage(Tree, pageNo) LockBTreePtr(Tree, pPtr) ] and ReadStackAndLockBTreePtr(Tree, PathStk, pPtr) be [ @pPtr = ReadStack(Tree, PathStk) LockBTreePtr(Tree, pPtr) ] and PopStack(PathStk) be PathStk>>PS.PathStkTop = PathStk>>PS.PathStkTop-1