{$TPO-} // treat all pointer inc/dec's as byte adjusts unit lists; interface type int8 = shortInt; int16 = smallInt; int32 = longInt; uInt8 = byte; uInt16 = word; uInt32 = dWord; type treeTypes = (BT_CUSTOM, BT_PCHAR, BT_STRING, BT_SINGLE, BT_DOUBLE, BT_INT32, BT_INT64); PbNode = ^TbNode; PbNodeData = ^TbNodeData; TBlockRun = packed record AG : int32; start : uInt16; len : uInt16; end; inodeAddr = TblockRun; uPtr = packed record case integer of 0: (i : int32); 1: (u : uInt32); 2: (s : single); 3: (d : double); 4: (p : pointer); 5: (bPtr : PbNode); 6: (offset : int64); 7: (iAddr : inodeAddr); end; PQueue = ^TQueue; TQueue = object private queue:pointer; elements:uInt32; maxElements:uInt32; size:uInt32; head, tail:uInt32; public constructor init(elementCount, elementSize:uInt32); function dequeue(var item):boolean; function enqueue(var item):boolean; function getElementCount:uInt32; destructor done; end; // TQueue PBTreeVFS = ^TBTreeVFS; TBTreeVFS = object public constructor init; function fClose:boolean; virtual; function fCreate(const filename:string):boolean; virtual; function fExist(const filename:string):boolean; virtual; function fOpen(const filename:string):boolean; virtual; function fRead(var buf; size:uInt32):boolean; virtual; function fSeek(offset:uInt32):boolean; virtual; function fWrite(var buf; size:uInt32):boolean; virtual; destructor done; virtual; end; // TBTreeVFS TbNode = packed record magic : uInt32; tag : uInt32; numKeys : uInt32; size : uInt32; left : uPtr; right : uPtr; parent : uPtr; reserved : uPtr; data : array[0..0] of byte; end; TbNodeData = packed record link : uPtr; key : record case treeTypes of BT_PCHAR, BT_STRING: (str:string[1]); BT_SINGLE: (s:single); BT_DOUBLE: (d:double); BT_INT32 : (i:int32); BT_INT64 : (x:int64); end; {record} end; {TbNode} type BTreeSearchRec = record value : uPtr; key : pointer; keySize:integer; end; {BTreeSearchRec} type PBTreeInfo = ^BTreeInfo; BTreeInfo = packed record magic : int64; root : uPtr; firstDeleted : uPtr; nodes : uInt32; height : uInt32; keys : uInt32; bNodeSize : uInt32; treeType : treeTypes; end; {BTreeHeader} type compareKeyFunc = function(key1, key2:pointer):integer; type copyKeyProc = procedure(srcKey, destKey:pointer); type keySizeFunc = function(key:pointer):integer; type PbTree = ^bTree; bTree = object private treeName : string; compareKeys : compareKeyFunc; copyKey : copyKeyProc; keySize : keySizeFunc; info : PBTreeInfo; vfs : PBTreeVFS; //dataFile : file; memoryTree : boolean; treeChanged : boolean; function align(keyLength:integer):integer; function allocEmptyNode:uPtr; function calcSize(node:PbNode):integer; procedure deAllocNode(node:PbNode); function deleteEntry(u:uPtr; key:pointer; value:uPtr):uPtr; procedure discardPage(node:PbNode); function fetchFirstKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; function fetchLastKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; function findLeafNode(u:uPtr; key:pointer):uPtr; procedure freeAll; procedure freePage(u:uPtr); function getAddress(node:PbNode):uPtr; function getRoot:uPtr; function insertEntry(u:uPtr; key:pointer; value:uPtr):boolean; function isNull(u:uPtr):boolean; function loadPage(u:uPtr):PbNode; procedure loadSuperBlock; procedure Print(var f:text; u:uPtr); procedure PrintInfo(var f:text; u:uPtr); function replaceEntry(u:uPtr; key:pointer; value:uPtr):boolean; procedure savePage(node:PbNode); procedure saveSuperBlock; procedure setLeft(u:uPtr; newPtr:uPtr); procedure setNull(var u:uPtr); procedure setRight(u:uPtr; newPtr:uPtr); procedure setParent(u:uPtr; newPtr:uPtr); public constructor init(const filename:string; nodeSize:uInt32; tt:treeTypes; virtualFS:PBTreeVFS); constructor open(const filename:string; virtualFS:PBTreeVFS); function Delete(key:pointer; value:uPtr):uPtr; function Insert(key:pointer; value:uPtr):boolean; procedure InstallUserFunctions(cmpFunc:compareKeyFunc; copyProc:copyKeyProc; ksFunc:keySizeFunc); function Find(key:pointer; var u:uPtr):boolean; procedure FindClose(var searchRec:BTreeSearchRec); function FindFirst(key:pointer; var searchRec:BTreeSearchRec):boolean; function FindNext(var searchRec:BTreeSearchRec):boolean; function FindPrev(var searchRec:BTreeSearchRec):boolean; function GetFirstKey(var searchRec:BTreeSearchRec):boolean; function GetKeyCount:uInt32; function GetLastKey(var searchRec:BTreeSearchRec):boolean; procedure PrintList(const filename:string); procedure PrintWholeTree(const filename:string); destructor done; end; // bTree implementation uses strings, dos; const B_NODE_LEAF = $4641454C; // LEAF const B_NODE_INDEX = $58444E49; // INDX const B_NODE_OPEN = $4E45504F; // OPEN const B_NODE_TREEINFO = $4F464E4945455254; // TREEINFO type PStandardVFS = ^TStandardVFS; TStandardVFS = object(TBTreeVFS) private dataFile:file; openFile:boolean; public constructor init; function fClose:boolean; virtual; function fCreate(const filename:string):boolean; virtual; function fExist(const filename:string):boolean; virtual; function fOpen(const filename:string):boolean; virtual; function fRead(var buf; size:uInt32):boolean; virtual; function fSeek(offset:uInt32):boolean; virtual; function fWrite(var buf; size:uInt32):boolean; virtual; destructor done; virtual; end; // TStandardVFS // TVFS virtual abstract functions constructor TBTreeVFS.init; begin writeln('Don''t call the parent constructor'); end; // TBTreeVFS.init function TBTreeVFS.fClose; result := FALSE; function TBTreeVFS.fCreate; result := FALSE; function TBTreeVFS.fExist; result := FALSE; function TBTreeVFS.fOpen; result := FALSE; function TBTreeVFS.fRead; result := FALSE; function TBTreeVFS.fSeek; result := FALSE; function TBTreeVFS.fWrite; result := FALSE; destructor TBTreeVFS.done; begin writeln('Don''t call the parent destructor'); end; // TBTreeVFS.done // TStandardVFS methods constructor TStandardVFS.init; begin openFile := FALSE; end; // TStandardVFS.init function TStandardVFS.fClose; begin if (openFile) then {$I-} close(dataFile); {$I+} openFile := (IOResult() <> 0); result := not openFile end; // TStandardVFS.fClose function TStandardVFS.fCreate; begin assign(dataFile, filename); {$I-} rewrite(dataFile, 1); {$I+} openFile := (IOResult() = 0); result := openFile end; // TStandardVFS.fCreate function TStandardVFS.fExist; begin result := (fSearch(filename, '') <> '') end; // TStandardVFS.fExist function TStandardVFS.fOpen; begin assign(dataFile, filename); {$I-} reset(dataFile, 1); {$I+} openFile := (IOResult() = 0); result := openFile end; // TStandardVFS.fOpen function TStandardVFS.fRead; var lResult:uInt32; begin blockread(dataFile, buf, size, lResult); result := ((IOResult() = 0) and (lResult = size)); end; // TStandardVFS.fRead function TStandardVFS.fSeek; begin {$I-} seek(dataFile, offset); {$I+} result := (IOResult() = 0) end; // TStandardVFS.fSeek function TStandardVFS.fWrite; var lResult:uInt32; begin // writeln('fWrite(dataFile, buf, ', count, ') @ position: ', filePos(dataFile)); blockwrite(dataFile, buf, size, lResult); result := ((IOResult() = 0) and (lResult = size)); end; // TStandardVFS.fWrite destructor TStandardVFS.done; begin if (openFile) then fClose(); end; // TStandardVFS.done function compareKeyNull(key1, key2:pointer):integer; result := 0; // compareKeyNull procedure copyKeyNull(srcKey, destKey:pointer); begin end; // copyKeyNull function keySizeNull(key:pointer):integer; result := 0; // keySizeNull (* * CompareKey functions * *) function compareKeyString(key1, key2:pointer):integer; begin result := 0; if ((key1 = NIL) or (key2 = NIL)) then exit; if string(key1^) < string(key2^) then result := -1 else if string(key1^) > string(key2^) then result := 1; end; {comapreKeyString} function compareKeyPChar(key1, key2:pointer):integer; begin result := 0; if ((key1 = NIL) or (key2 = NIL)) then exit; result := strcomp(pchar(key1), pchar(key2)); end; {compareKeyPChar} function compareKeyDouble(key1, key2:pointer):integer; begin result := 0; if ((key1 = NIL) or (key2 = NIL)) then exit; if (double(key1^) < double(key2^)) then result := -1 else if (double(key1^) > double(key2^)) then result := 1; end; {compareKeyDouble} function compareKeySingle(key1, key2:pointer):integer; begin result := 0; if ((key1 = NIL) or (key2 = NIL)) then exit; if (single(key1^) < single(key2^)) then result := -1 else if (single(key1^) > single(key2^)) then result := 1; end; {compareKeySingle} function compareKeyInt32(key1, key2:pointer):integer; begin result := 0; if ((key1 = NIL) or (key2 = NIL)) then exit; if (int32(key1^) < int32(key2^)) then result := -1 else if (int32(key1^) > int32(key2^)) then result := 1; end; {compareKeyInt32} function compareKeyInt64(key1, key2:pointer):integer; begin result := 0; if ((key1 = NIL) or (key2 = NIL)) then exit; if (int64(key1^) < int64(key2^)) then result := -1 else if (int64(key1^) > int64(key2^)) then result := 1; end; // compareKeyInt64 (* * CopyKey functions * *) procedure copyKeyPChar(srcKey, destKey:pointer); begin if ((srcKey = NIL) or (destKey = NIL)) then exit; strcopy(pchar(destKey), pchar(srcKey)); end; {copyeKeyPChar} procedure copyKeyString(srcKey, destKey:pointer); begin if ((srcKey = NIL) or (destKey = NIL)) then exit; string(destKey^) := string(srcKey^); end; {copyKeyString} procedure copyKeySingle(srcKey, destKey:pointer); begin if ((srcKey = NIL) or (destKey = NIL)) then exit; single(destKey^) := single(srcKey^); end; {copyKeySingle} procedure copyKeyDouble(srcKey, destKey:pointer); begin if ((srcKey = NIL) or (destKey = NIL)) then exit; double(destKey^) := double(srcKey^); end; {copyKeyDouble} procedure copyKeyInt32(srcKey, destKey:pointer); begin if ((srcKey = NIL) or (destKey = NIL)) then exit; int32(destKey^) := int32(srcKey^); end; {copyKeyInt32} procedure copyKeyInt64(srcKey, destKey:pointer); begin if ((srcKey = NIL) or (destKey = NIL)) then exit; int64(destKey^) := int64(srcKey^); end; {copyKeyInt64} (* * KeySize functions * *) function keySizePChar(key:pointer):integer; begin result := 0; if (key = NIL) then exit; result := strlen(pchar(key))+1; end; {keySizePChar} function keySizeString(key:pointer):integer; begin result := 0; if (key = NIL) then exit; result := length(string(key^))+1; end; {keySizeString} function keySizeSingle(key:pointer):integer; begin if (key = NIL) then result := 0 else result := sizeof(single); end; {keySizeSingle} function keySizeDouble(key:pointer):integer; begin if (key = NIL) then result := 0 else result := sizeof(double); end; {keySizeDouble} function keySizeInt32(key:pointer):integer; begin if (key = NIL) then result := 0 else result := sizeof(int32); end; {keySizeInt32} function keySizeInt64(key:pointer):integer; begin if (key = NIL) then result := 0 else result := sizeof(int64); end; {keySizeInt64} (********************************) function compareUPtrs(u1, u2:uPtr):boolean; begin result := (u1.offset = u2.offset) end; // compareUPtrs overload = = compareUPtrs; (* * * B^Tree object methods * *) constructor bTree.init; var lResult:longint; begin memoryTree := fileName = ''; treeName := filename; // allocate the info section // getmem(info, sizeof(BTreeInfo)); new(info); if (info = NIL) then runError; fillchar(info^, sizeof(BTreeInfo), 0); vfs := virtualFS; with info^ do begin magic := B_NODE_TREEINFO; setNull(root); setNull(firstDeleted); nodes := 0; height := 0; keys := 0; bNodeSize := nodeSize; treeType := tt; end; {with} if not (memoryTree) then begin if (vfs = NIL) then vfs := new(PStandardVFS, init); if (vfs^.fExist(filename)) then begin if not (vfs^.fOpen(filename)) then runError; if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTreeInfo'); end else begin if not vfs^.fCreate(filename) then runError; if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error writing bTreeInfo'); end; // if not vfs^.fClose() then runError; // if not (vfs^.fOpen(filename, info^.bNodeSize)) then runError; end; case info^.treeType of BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); else InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); end; // case treeChanged := FALSE; // The superblock of the tree hasn't been modified end; // bTree.init constructor bTree.open; var lResult:longint; label safeExit; begin info := NIL; memoryTree := FALSE; treeChanged := FALSE; // The superblock of the tree hasn't been modified treeName := filename; InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); //if (filename = '') or (fsearch(filename, '') = '') then begin writeln(filename, ' does not exist'); exit; end; // allocate the info section vfs := virtualFS; if (vfs = NIL) then vfs := new(PStandardVFS, init); if not vfs^.fExist(filename) then begin writeln('in bTree.open(): file does not exist'); goto safeExit; end; new(info); fillchar(info^, sizeof(info^), 0); if not vfs^.fOpen(filename) then runError; if not vfs^.fRead(info^, sizeof(BTreeInfo)) then runError; if (info^.magic <> B_NODE_TREEINFO) then runError; // if not vfs^.fClose() then runError; // vfs^.fOpen(filename, info^.bNodeSize); case info^.treeType of BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); end; // case safeExit: end; // bTree.open function bTree.align; begin result := ((sizeof(uptr) + keyLength + 7) shr 3) shl 3; end; {bTree.align} function bTree.allocEmptyNode; var newNode:PbNode; begin saveSuperBlock(); setNull(result); if (not isNull(info^.firstDeleted)) then begin newNode := loadPage(info^.firstDeleted); if (newNode = NIL) then exit; info^.firstDeleted := newNode^.parent; end else begin getmem(newNode, info^.bNodeSize); fillchar(newNode^, info^.bNodeSize, 0); inc(info^.nodes); newNode^.tag := info^.nodes; end; with newNode^ do begin magic := B_NODE_LEAF; numKeys := 1; size := dword(@newNode^.data) - dword(newNode); setNull(left); setNull(right); setNull(parent); setNull(reserved); end; {with} result := getAddress(newNode); savePage(newNode); discardPage(newNode); // loadSuperBlock(); end; {bTree.allocEmptyNode} function bTree.calcSize; var size:integer; curNode:integer; dataNode:PbNodeData; begin if (node = NIL) then exit; dataNode := @node^.data; size := dword(dataNode) - dword(node); for curNode := 1 to node^.numKeys do begin inc(size, align(keySize(@dataNode^.key))); inc(dataNode, align(keySize(@dataNode^.key))); end; if (node^.magic = B_NODE_INDEX) then inc(size, sizeof(uPtr)); result := size; end; procedure bTree.deAllocNode; begin if (node = NIL) then exit; with node^ do begin magic := B_NODE_OPEN; setNull(left); setNull(right); setNull(reserved); if not memoryTree then parent := info^.firstDeleted; size := dword(@node^.data) - dword(node); fillchar(data, info^.bNodeSize - size, 0); end; if not memoryTree then info^.firstDeleted := getAddress(node); savePage(node); freemem(node, info^.bNodeSize); end; {btree.deAllocNode} function bTree.deleteEntry; var bnode, tmpNode, neighbor, parent : PbNode; neighborSize:integer; dataNode, nextDataNode, tmpDataNode:PbNodeData; src, dst : pointer; tmpLink:uPtr; nodeSize:uInt32; curNode, res, size: integer; tmpTag:uInt32; found : boolean; procedure getNeighbor; var l, r:PbNode; begin neighbor := NIL; neighborSize := 0; l := loadPage(bnode^.left); r := loadPage(bnode^.right); if (l = NIL) then begin // this next check may be unecessary if (r^.parent = bnode^.parent) then begin neighbor := r; neighborSize := r^.size; end; exit end; if (r = NIL) then begin // this next check may be unecessary if (l^.parent = bnode^.parent) then begin neighbor := l; neighborSize := l^.size; end; exit end; if (l^.parent = r^.parent) then // left/right neighbors are children of the same parent begin if (l^.size < r^.size) then begin neighbor := l; neighborSize := l^.size; discardPage(r); exit end else begin neighbor := r; neighborSize := r^.size; discardPage(l); exit end; end else // one of the neighbors has a different parent begin if (l^.parent = bnode^.parent) then begin neighbor := l; neighborSize := l^.size; discardPage(r); exit end else begin neighbor := r; neighborSize := r^.size; discardPage(l); exit end; end; discardPage(l); discardPage(r); end; {getNeighbor local function} begin setNull(result); if (isNull(u)) then exit; bnode := loadPage(u); if (bnode^.numKeys = 0) then runError; {this should never happen} if (bnode^.magic = B_NODE_LEAF) then begin { case info^.treeType of BT_STRING:writeln(f, 'Deleting ', string(key^), ' size: ', align(keySize(key))); BT_PCHAR:writeln(f, 'Deleting ', pchar(key)); end;} dataNode := @bnode^.data; curNode := 0; repeat res := compareKeys(@dataNode^.key, key); if (res >= 0) then break; inc(dataNode, align(keySize(@dataNode^.key))); inc(curNode); until (curNode = bnode^.numKeys); if (res <> 0) then begin // writeln(f, 'Could not find in leaf node'); discardPage(bnode); exit end; // Since we found the entry, we will have to save the // superblock at the end. Set treeChanged to TRUE treeChanged := TRUE; result := dataNode^.link; nodeSize := align(keySize(@dataNode^.key)); nextDataNode := dataNode; inc(nextDataNode, nodeSize); move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); dec(bnode^.numKeys); dec(bnode^.size, nodeSize); dataNode := pointer(dword(bnode) + bnode^.size); fillchar(dataNode^, nodeSize, 0); dec(info^.keys); // check for merges and other stuff if (getAddress(bnode) = info^.root) then begin if (bnode^.numKeys = 0) then begin setNull(info^.root); dec(info^.height); deAllocNode(bnode); exit; end; end else // not root begin if (bnode^.size < (info^.bNodeSize / 2)) then begin getNeighbor; if (neighbor <> NIL) and (neighborSize <> 0) then begin if (bnode^.size + neighborSize <= info^.bNodeSize) then begin //writeln(f, 'Merging leaf pages'); // if neighbor is a predecessor of bnode then swap them if (getAddress(neighbor) = bnode^.left) then begin tmpNode := bnode; bnode := neighbor; neighbor := tmpNode; end; // merge the two nodes together // bnode is the left node, neighbor is the right node src := @neighbor^.data; dst := pointer(dword(bnode) + bnode^.size); size := neighbor^.size - (dword(src) - dword(neighbor)); move(src^, dst^, size); inc(bnode^.size, size); inc(bnode^.numKeys, neighbor^.numKeys); setRight(bnode^.left, getAddress(neighbor)); bnode^.right := neighbor^.right; tmpTag := neighbor^.tag; // save tag info move(bnode^, neighbor^, info^.bNodeSize); neighbor^.tag := tmpTag; // restore tag info savePage(neighbor); deleteEntry(bnode^.parent, NIL, getAddress(bnode)); discardPage(neighbor); deAllocNode(bnode); exit; end //if (bnode^.size + neighborSize <= info^.bNodeSize) else // "acquire" an entry from a neighbor begin if (getAddress(neighbor) = bnode^.right) then begin //writeln(f, 'acquiring from leaf bnode/neighbor'); // neighbor node is to the right. Append an entry from it to bnode dataNode := @neighbor^.data; src := dataNode; dst := pointer(dword(bnode) + bnode^.size); nodeSize := align(keySize(@dataNode^.key)); move(src^, dst^, nodeSize); inc(bnode^.size, nodeSize); inc(bnode^.numKeys); nextDataNode := dataNode; inc(nextDataNode, nodeSize); move(nextDataNode^, dataNode^, neighbor^.size - (dword(dataNode) - dword(neighbor)) - nodeSize); dec(neighbor^.size, nodeSize); dec(neighbor^.numKeys); // clear off the end of the neighbor node dataNode := pointer(dword(neighbor) + neighbor^.size); fillchar(dataNode^, nodeSize, 0); savePage(bnode); savePage(neighbor); dataNode := dst; replaceEntry(bnode^.parent, @dataNode^.key, getAddress(bnode)); end else begin //writeln(f, 'acquiring from leaf neighbor/bnode'); // neighbor node is to the left. Take the last entry and prepend to bnode dataNode := @neighbor^.data; for curNode := 1 to neighbor^.numKeys-1 do inc(dataNode, align(keySize(@dataNode^.key))); // dataNode (hopefully) now points to the last key/value pair in neighbor nodeSize := align(keySize(@dataNode^.key)); src := @bnode^.data; dst := src; inc(dst, nodeSize); move(src^, dst^, bnode^.size - (dword(src) - dword(bnode))); dst := src; src := dataNode; move(src^, dst^, nodeSize); fillchar(src^, nodeSize, 0); dec(neighbor^.size, nodeSize); dec(neighbor^.numKeys); inc(bnode^.size, nodeSize); inc(bnode^.numKeys); savePage(bnode); savePage(neighbor); // this can be optimized by storing the previous // node from the for loop above dataNode := @neighbor^.data; for curNode := 1 to neighbor^.numKeys-1 do inc(dataNode, align(keySize(@dataNode^.key))); replaceEntry(neighbor^.parent, @dataNode^.key, getAddress(neighbor)); end; // else discardPage(bnode); discardPage(neighbor); exit; end; end; //if (neighbor <> NIL) and (neighborSize <> 0) discardPage(neighbor); end; // if (bnode^.size < (info^.bNodeSize / 2)) end; {else not root} end else {not a leaf} begin dataNode := @bnode^.data; nextDataNode := dataNode; nodeSize := align(keySize(@dataNode^.key)); inc(nextDataNode, nodeSize); found := FALSE; for curNode := 1 to bnode^.numKeys do begin found := (dataNode^.link = value); if (found) then break; if (curNode <> bnode^.numKeys) then begin dataNode := nextDataNode; nodeSize := align(keySize(@nextDataNode^.key)); inc(nextDataNode, nodeSize); end; end; if (not found) then begin if (nextDataNode^.link = value) then nextDataNode^.link := dataNode^.link else begin // writeln(f, 'Could not find in index node'); discardPage(bnode); exit; end; end; {if (dataNode = nextDataNode) then inc(nextDataNode, align(keySize(@dataNode^.key)));} // Copy this link into the next link so we can just delete this node {nextDataNode^.link := dataNode^.link;} fillchar(dataNode^, nodeSize, 0); move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); dec(bnode^.numKeys); dec(bnode^.size, nodeSize); dst := pointer(dword(bnode) + bnode^.size); fillchar(dst^, nodeSize, 0); // check for merges and other stuff if (getAddress(bnode) = getRoot()) then begin if (bnode^.numKeys = 0) then begin dataNode := @bnode^.data; info^.root := dataNode^.link; dec(info^.height); deAllocNode(bnode); bnode := loadPage(info^.root); setNull(bnode^.parent); savePage(bnode); discardPage(bnode); exit end; end else {not root} begin if (bnode^.size < (info^.bNodeSize / 2)) then begin getNeighbor; if (neighbor<>NIL) and (neighborSize<>0) then begin if (bnode^.size + neighborSize <= info^.bNodeSize) then begin //writeln(f, 'merging index pages'); // if neighbor is a predecessor of bnode then swap them if (getAddress(neighbor) = bnode^.left) then begin tmpNode := bnode; bnode := neighbor; neighbor := tmpNode; end; // We need to get the key that currently resides between // the bnode (left) link and the neighbor (right) link in the parent parent := loadPage(bnode^.parent); if (parent = NIL) then runError; // this would be bad dataNode := @parent^.data; for curNode := 0 to parent^.numKeys-1 do begin if (dataNode^.link = getAddress(bnode)) then break; inc(dataNode, align(keySize(@dataNode^.key))); end; {for} // theoretically dataNode^.key is the key between the two links size := align(keySize(@dataNode^.key)); if ((bnode^.size + neighbor^.size + size - (dword(@bnode^.data) - dword(bnode))) > info^.bNodeSize) then begin // The key wouldn't fit. bail discardPage(parent); savePage(neighbor); savePage(bnode); discardPage(neighbor); discardPage(bnode); exit; end; // copy the key to the end of bnode src := @dataNode^.key; dst := pointer(dword(bnode) + bnode^.size); move(src^, dst^, keySize(@dataNode^.key)); inc(bnode^.numKeys); // we just added a key dec(size, sizeof(uPtr)); inc(bnode^.size, size); // increment by aligned size inc(dst, size); // also increment the dst by aligned size discardPage(parent); // don't need the parent any more // merge the two nodes together // bnode is the left node, neighbor is the right node src := @neighbor^.data; // dst := pointer(dword(bnode) + bnode^.size); // set above size := neighbor^.size - (dword(src) - dword(neighbor)); move(src^, dst^, size); inc(bnode^.size, size); inc(bnode^.numKeys, neighbor^.numKeys); dataNode := @bnode^.data; for curNode := 0 to bnode^.numKeys do begin setParent(dataNode^.link, getAddress(neighbor)); inc(dataNode, align(keySize(@dataNode^.key))); end; {for} setRight(bnode^.left, getAddress(neighbor)); bnode^.right := neighbor^.right; tmpTag := neighbor^.tag; // save tag info move(bnode^, neighbor^, info^.bNodeSize); neighbor^.tag := tmpTag; // restore tag info savePage(neighbor); deleteEntry(bnode^.parent, NIL, getAddress(bnode)); deAllocNode(bnode); discardPage(neighbor); exit; end else // "acquire" an entry from a neighbor begin // we need to do one extra check if (getAddress(neighbor) = bnode^.right) then begin tmpLink := getAddress(bnode); tmpDataNode := @neighbor^.data; end else begin tmpLink := getAddress(neighbor); tmpDataNode := @neighbor^.data; for curNode := 1 to neighbor^.numKeys-1 do inc(dataNode, align(keySize(@tmpDataNode^.key))); end; if (neighbor^.size - align(keySize(@tmpDataNode^.key)) < info^.bNodeSize / 2) then begin savePage(bnode); discardPage(bnode); discardPage(neighbor); exit end; // get the key between the neighbor/bnode pair parent := loadPage(bnode^.parent); if (parent = NIL) then runError; // this would be bad dataNode := @parent^.data; for curNode := 1 to parent^.numKeys do begin if (dataNode^.link = tmpLink) then break; inc(dataNode, align(keySize(@dataNode^.key))); end; {for} // theoretically dataNode^.key is the key between the two links nodeSize := align(keySize(@dataNode^.key)); if (getAddress(neighbor) = bnode^.right) then begin //writeln(f, 'acquiring from index bnode/neighbor'); savePage(neighbor); // hmm.. did we change neighbor? src := @dataNode^.key; dataNode := @neighbor^.data; savePage(bnode); insertEntry(getAddress(bnode), src, dataNode^.link); replaceEntry(getAddress(parent), @dataNode^.key, getAddress(bnode)); deleteEntry(getAddress(neighbor), NIL, dataNode^.link); end else // neighbor is to the left, bnode is to the right begin //writeln(f, 'acquiring from index neighbor/bnode'); // We need to take the key from the parent node, coupled with the // last value in the neighbor, and insert them into bnode // To do this we have to move everything in bnode over src := @dataNode^.key; nodeSize := align(keySize(@dataNode^.key)); dataNode := @bnode^.data; nextDataNode := dataNode; inc(nextDataNode, nodeSize); move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bnode))); // copy in the key fillchar(dataNode^, nodeSize, 0); move(src^, dataNode^.key, keySize(src)); // now we update the size and keys inc(bnode^.size, nodeSize); inc(bnode^.numKeys); // we still have to insert the value and update the child's parent ptr tmpDataNode := dataNode; dataNode := @neighbor^.data; for curNode := 1 to neighbor^.numKeys-1 do inc(dataNode, align(keySize(@dataNode^.key))); nextDataNode := dataNode; inc(nextDataNode, align(keySize(@dataNode^.key))); // insert the value tmpDataNode^.link := nextDataNode^.link; setParent(tmpDataNode^.link, getAddress(bnode)); // need to change the insert (don't forget to update the child's parent ptr) // insertEntry(getAddress(bnode), src, nextDataNode^.link); savePage(bnode); savePage(neighbor); replaceEntry(getAddress(parent), @dataNode^.key, getAddress(neighbor)); deleteEntry(getAddress(neighbor), NIL, nextDataNode^.link); end; discardPage(bnode); discardPage(neighbor); discardPage(parent); exit; end; discardPage(neighbor); end; // if (bnode^.size + neighborSize <= info^.bNodeSize) then end; // if (bnode^.size < (info^.bNodeSize / 2)) then end; // else not root end; // not a leaf savePage(bnode); discardPage(bnode); end; {bTree.deleteEntry} procedure bTree.discardPage; begin // disk based trees allocate temporary memory to hold a page. So, if we're not // a memory tree, dispose of the temp page if not (memoryTree) then if (node <> NIL) then freemem(node, info^.bNodeSize); end; {bTree.discardPage} function bTree.getAddress; var u:uPtr; begin setNull(u); if (memoryTree) then u.bPtr := node else u.offset := node^.tag; { * bNodeSize;} result := u; end; {bTree.getAddress} function bTree.getRoot; begin if (info = NIL) then setNull(result) else result := info^.root; end; // bTree.getRoot function bTree.insertEntry; var tmpUPtr, tmpRoot:uPtr; dataNode, nextDataNode, prevDataNode:PbNodeData; midKey:BTreeSearchRec; keyLength, curNode, dataSize, res:integer; bnode, newNode, rootNode:PbNode; nodeSize:uInt32; begin result := FALSE; if (key = NIL) then exit; { case info^.treeType of BT_STRING:writeln(f, 'Inserting ', string(key^)); BT_PCHAR:writeln(f, 'Inserting ', pchar(key)); end;} midKey.key := NIL; midKey.keySize := 0; keyLength := keySize(key); if (keyLength = 0) or (keyLength > info^.bNodeSize) then exit; // The superblock will have to be updated when the tree is destructed // set treeChanged to TRUE treeChanged := TRUE; nodeSize := align(keyLength); if (isNull(info^.root)) then begin (* * This case should only occur once when a directory is first created. *) tmpRoot := allocEmptyNode(); // info^.root := allocEmptyNode(); if (isNull(tmpRoot)) then runError; rootNode := loadPage(tmpRoot); info^.root := tmpRoot; inc(info^.keys); inc(info^.height); rootNode^.magic := B_NODE_LEAF; dataNode := @rootNode^.data; fillchar(dataNode^, nodeSize, 0); dataNode^.link := value; copyKey(key, @dataNode^.key); inc(rootNode^.size, nodeSize); {hm.. should I add the sizeof the link?} savePage(rootNode); discardPage(rootNode); result := TRUE; exit end; bnode := loadPage(u); if (bnode^.size + nodeSize <= info^.bNodeSize) then begin dataNode := @bnode^.data; curNode := 0; repeat res := compareKeys(key, @dataNode^.key); if (res <= 0) then break; inc(dataNode, align(keySize(@dataNode^.key))); inc(curNode); until curNode = bnode^.numKeys; if (res = 0) then begin dataNode^.link := value; savePage(bnode); discardPage(bnode); result := TRUE; exit; end; nextDataNode := dataNode; inc(nextDataNode, nodeSize); {cast to byte size records} move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); fillchar(dataNode^, nodeSize, 0); copyKey(key, @dataNode^.key); dataNode^.link := value; if (bnode^.magic = B_NODE_INDEX) then begin if (not isNull(dataNode^.link)) then setParent(dataNode^.link, getAddress(bnode)); { if (curNode = bnode^.numKeys) then begin} tmpUPtr := dataNode^.link; dataNode^.link := nextDataNode^.link; nextDataNode^.link := tmpUPtr; { end;} end; inc(bnode^.numKeys); if (bnode^.magic = B_NODE_LEAF) then inc(info^.keys); inc(bNode^.size, nodeSize); savePage(bnode); end else begin newNode := loadPage(allocEmptyNode); if (newNode = NIL) then runError; newNode^.magic := bnode^.magic; newNode^.left := getAddress(bnode); newNode^.right := bnode^.right; newNode^.parent := bnode^.parent; if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); bnode^.right := getAddress(newNode); dataNode := @bnode^.data; {for curNode:=0 to (bnode^.numKeys div 2)-1 do inc(dataNode, align(keySize(@dataNode^.key)));} curNode := 1; repeat prevDataNode := dataNode; inc(dataNode, align(keySize(@dataNode^.key))); inc(curNode); until (dword(dataNode) - dword(bnode) >= bnode^.size shr 1); {midKey := dataNode;} dataNode := prevDataNode; midKey.keySize := keySize(@dataNode^.key); GetMem(midKey.key, midKey.KeySize); {if (midKey.key = NIL) then halt;} copyKey(@dataNode^.key, midKey.key); dec(curNode); if (bnode^.magic = B_NODE_LEAF) then begin inc(dataNode, align(keySize(@dataNode^.key))); dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); move(dataNode^, newNode^.data, dataSize); fillchar(dataNode^, dataSize, 0); dec(bnode^.size, dataSize); newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); newNode^.numKeys := bnode^.numKeys - curNode; dec(bnode^.numKeys, newNode^.numKeys); savePage(bnode); savePage(newNode); if (compareKeys(key, midKey.key) <= 0) then insertEntry(getAddress(bnode), key, value) else insertEntry(getAddress(newNode), key, value); end else // not a leaf begin inc(dataNode, align(keySize(@dataNode^.key))); dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); move(dataNode^, newNode^.data, dataSize); fillchar(dataNode^, dataSize, 0); dec(bnode^.size, dataSize); // we need to adjust the size because we're taking out the midkey // and we're also adding that extra pointer dec(bnode^.size, align(keySize(midkey.key))); inc(bnode^.size, sizeof(uPtr)); newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); newNode^.numKeys := bnode^.numKeys - curNode; dec(bnode^.numKeys, newNode^.numKeys); dec(bnode^.numKeys); savePage(bnode); savePage(newNode); dataNode := @newNode^.data; for curNode := 0 to newNode^.numKeys do begin setParent(dataNode^.link, getAddress(newNode)); inc(dataNode, align(keySize(@dataNode^.key))); end; //savePage(bnode); if (compareKeys(key, midKey.key) <= 0) then insertEntry(getAddress(bnode), key, value) else insertEntry(getAddress(newNode), key, value); end; (* * If we were the root node, then create a new one *) if (getAddress(bnode) = info^.root) then begin tmpRoot := allocEmptyNode(); // info^.root := allocEmptyNode; rootNode := loadPage(tmpRoot); info^.root := tmpRoot; setParent(getAddress(bnode), info^.root); setParent(getAddress(newNode), info^.root); inc(info^.height); {dataNode := @newNode^.data; } dataNode := @rootNode^.data; with rootNode^ do begin magic := B_NODE_INDEX; {numKeys := 1;} dataNode^.link := getAddress(bnode); keyLength := midKey.keySize; copyKey(midKey.key, @dataNode^.key); inc(size, align(keyLength)); inc(size, sizeof(uPtr)); inc(dataNode, align(keyLength)); dataNode^.link := getAddress(newNode); end; {with} savePage(rootNode); discardPage(rootNode); end {if oldNode = root} else begin insertEntry(bnode^.parent, midKey.key, getAddress(newNode)); end; if (midKey.key <> NIL) and (midKey.keySize <> 0) then FreeMem(midKey.key, midKey.keySize); discardPage(newNode); end; {if out of space in this node} discardPage(bnode); result := TRUE end; {bTree.insertEntry} function bTree.isNull; begin {if (memoryTree) then result := (u.bPtr = NIL) else} result := (u.offset = 0) end; {bTree.isNull} function bTree.loadPage; var node:PbNode; begin result := NIL; if isNull(u) then exit; if (memoryTree) then node := u.bPtr else begin if not vfs^.fSeek(dword(u.offset) * info^.bNodeSize) then runError; {exit} getmem(node, info^.bNodeSize); if (node = NIL) then exit; if not vfs^.fRead(node^, info^.bNodeSize) then runError; {exit;} end; // !memoryTree result := node end; // bTree.loadPage procedure bTree.loadSuperBlock; begin if memoryTree then exit; if not vfs^.fSeek(0) then writeln('Error seeking in bTree.loadSuperBlock'); if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTree Info in bTree.loadSuperBlock'); end; function bTree.replaceEntry; var dataNode:PbNodeData; node:PbNode; curNode, res, srcKeySize, dstKeySize, deltaSize:integer; src, dst:pointer; begin result := FALSE; if ((key = NIL) or (isNull(u))) then exit; node := loadPage(u); if (node = NIL) then exit; if (node^.magic <> B_NODE_INDEX) then begin writeln('Error! Not in index nodes.'); discardPage(node); exit end; dataNode := @node^.data; // possible bug on count for curNode := 0 to node^.numKeys do begin if (dataNode^.link = value) then begin srcKeySize := keySize(key); dstKeySize := keySize(@dataNode^.key); deltaSize := align(srcKeySize) - align(dstKeySize); if (deltaSize = 0) then begin // source and dest keys are equal length. Replace old key with new key fillchar(dataNode^.key, dstKeySize, 0); move(key^, dataNode^.key, srcKeySize); savePage(node); end else if (deltaSize < 0) then begin // source key is smaller than dest key. fillchar(dataNode^.key, dstKeySize, 0); move(key^, dataNode^.key, srcKeySize); src := dataNode; dst := dataNode; inc(src, align(dstKeySize)); // no, this isn't backwards inc(dst, align(srcKeySize)); // we're copying from the old size move(src^, dst^, node^.size - (dword(src) - dword(node))); inc(node^.size, deltaSize); // deltaSize is negative savePage(node); end else begin // source key is bigger than dest key if ((deltaSize + node^.size) > info^.bNodeSize) then begin // writeln(f, 'Source key would cause split'); end else // source key will fit begin // dataNode points to the value/key pair src := dataNode; dst := dataNode; inc(src, align(dstKeySize)); inc(dst, align(srcKeySize)); move(src^, dst^, node^.size - (dword(src) - dword(node))); fillchar(dataNode^.key, dstKeySize, 0); move(key^, dataNode^.key, srcKeySize); inc(node^.size, deltaSize); savePage(node); end; end; break; end; inc(dataNode, align(keySize(@dataNode^.key))); end; discardPage(node); result := TRUE; end; {bTree.replaceEntry} procedure bTree.savePage; begin if (memoryTree) then exit; if (node = NIL) then exit; if not vfs^.fSeek(dword(node^.tag) * info^.bNodeSize) then writeln('Error seeking in bTree.savePage()'); if not vfs^.fWrite(node^, info^.bNodeSize) then writeln('Error writing page in bTree.savePage()'); end; // bTree.savePage procedure bTree.saveSuperBlock; begin if (not(memoryTree)) then begin // if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()'); // if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()'); if not vfs^.fSeek(0) then writeln('Error seeking in bTree.saveSuperBlock()'); // reset back to the beginning if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.saveSuperBlock()'); // disposing the vfs pointer will close the file as necessary end; end; // bTree.saveSuperBlock procedure bTree.setLeft; var node:PbNode; begin node := loadPage(u); if (node = NIL) then exit; node^.left := newPtr; savePage(node); discardPage(node); end; {bTree.setLeft} procedure bTree.setParent; var node:PbNode; begin node := loadPage(u); if (node = NIL) then exit; node^.parent := newPtr; savePage(node); discardPage(node); end; {bTree.setParent} procedure bTree.setNull; begin { if (memoryTree) then} u.offset := 0 {else u.offset := -1;} end; {bTree.setNull} procedure bTree.setRight; var node:PbNode; begin node := loadPage(u); if (node = NIL) then exit; node^.right := newPtr; savePage(node); discardPage(node); end; {bTree.setRight} function bTree.fetchFirstKey; var node:PbNode; dataNode:PbNodeData; begin result := FALSE; if isNull(u) then exit; node := loadPage(u); if (node = NIL) then exit; dataNode := @node^.data; if (node^.magic = B_NODE_LEAF) then begin searchRec.value := dataNode^.link; searchRec.keySize := keySize(@dataNode^.key); GetMem(searchRec.key, align(searchRec.keySize)); if (searchRec.key = NIL) then exit; fillchar(searchRec.key^, align(searchRec.keySize), 0); copyKey(@dataNode^.key, searchRec.key); result := TRUE; end else begin result := fetchFirstKey(dataNode^.link, searchRec); end; discardPage(node); end; {bTree.fetchFirstKey} function bTree.fetchLastKey; var node:PbNode; dataNode:PbNodeData; curNode:integer; begin result := FALSE; if isNull(u) then exit; node := loadPage(u); if (node = NIL) then exit; dataNode := @node^.data; if (node^.magic = B_NODE_LEAF) then begin for curNode := 1 to node^.numKeys-1 do inc(dataNode, align(keySize(@dataNode^.key))); searchRec.value := dataNode^.link; searchRec.keySize := keySize(@dataNode^.key); GetMem(searchRec.key, align(searchRec.keySize)); if (searchRec.key = NIL) then exit; fillchar(searchRec.key^, align(searchRec.keySize), 0); copyKey(@dataNode^.key, searchRec.key); result := TRUE; end else begin for curNode := 1 to node^.numKeys do inc(dataNode, align(keySize(@dataNode^.key))); result := fetchLastKey(dataNode^.link, searchRec); end; discardPage(node); end; {bTree.fetchLastKey} procedure bTree.findLeafNode; var bNode:PbNode; dataNode:PbNodeData; i:integer; link:uPtr; begin setNull(result); if (key = NIL) then exit; if (isNull(u)) then exit; bnode := loadPage(u); if (bnode^.magic = B_NODE_LEAF) then begin result := u; discardPage(bnode); exit; end else begin dataNode := @bnode^.data; for i:=0 to bnode^.numKeys -1 do begin if (compareKeys(key, @dataNode^.key) <= 0) then begin link := dataNode^.link; discardPage(bnode); result := findLeafNode(link, key); exit; end; inc(dataNode, align(keySize(@dataNode^.key))); end; {for} link := dataNode^.link; discardPage(bnode); result := findLeafNode(link, key); end; end; {bTree.findLeafNode} procedure bTree.freeAll; var node:PbNode; begin if not(memoryTree) then exit; freePage(getRoot()); setNull(info^.root); end; {bTree.freeAll} procedure bTree.freePage; var curNode:integer; dataNode:PbNodeData; node : PbNode; begin if isNull(u) then exit; node := u.bPtr; curNode := 0; if (node^.magic = B_NODE_INDEX) then begin dataNode := @node^.data; for curNode := 0 to node^.numKeys-1 do begin freePage(dataNode^.link); inc(dataNode, align(keySize(@dataNode^.key))); end; freePage(dataNode^.link); end; freemem(node, info^.bNodeSize); end; // bTree.freePage function bTree.Delete; begin result := deleteEntry(findLeafNode(getRoot(), key), key, value); end; {bTree.Delete} function bTree.Find; var bnode:PbNode; dataNode:PbNodeData; i, res:integer; begin result := FALSE; setNull(u); bnode := loadPage(findLeafNode(getRoot(), key)); if (bnode = NIL) then exit; {oops! serious problem} if (bnode^.magic <> B_NODE_LEAF) then writeln('Error! not in leaf nodes'); dataNode := @bnode^.data; for i:=0 to bnode^.numKeys-1 do begin res := compareKeys(@dataNode^.key, key); // strcomp(@dataNode^.key.str[1], pchar(key)); if (res >= 0) then break; inc(dataNode, align(keySize(@dataNode^.key))); end; {for i} // did we find it? if (res = 0) then begin u := dataNode^.link; result := TRUE; end; {if res = 0} discardPage(bnode); end; {bTree.Find} procedure bTree.FindClose; begin with searchRec do begin setNull(value); if (key<>NIL) then FreeMem(key, align(keySize)); key := NIL; keySize := 0; end; {with F} end; {bTree.FindClose} function bTree.FindFirst; var node:PbNode; dataNode:PbNodeData; curNode, res:integer; begin result := FALSE; with searchRec do begin setNull(value); key := NIL; keySize := 0 end; {with searchRec} if (key = NIL) then exit; node := loadPage(findLeafNode(getRoot(), key)); if (node = NIL) then exit; dataNode := @node^.data; curNode := 0; repeat res := compareKeys(key, @dataNode^.key); if (res <= 0) then break; inc(dataNode, align(keySize(@dataNode^.key))); inc(curNode); until curNode = node^.numKeys; if (res = 0) then // we found it begin searchRec.value := dataNode^.link; searchRec.keySize := keySize(@dataNode^.key); GetMem(searchRec.key, align(searchRec.keySize)); fillchar(searchRec.key^, align(searchRec.keySize), 0); if (searchRec.key = NIL) then exit; copyKey(@dataNode^.key, searchRec.key); result := TRUE; end; {if res = 0} discardPage(node); end; {bTree.FindFirst} function bTree.FindNext; var nextNode:uPtr; node:PbNode; dataNode:PbNodeData; curNode, res:integer; begin result := FALSE; if (searchRec.key = NIL) then exit; node := loadPage(findLeafNode(getRoot(), searchRec.key)); if (node = NIL) then begin findClose(searchRec); exit end; dataNode := @node^.data; curNode := 0; repeat res := compareKeys(@dataNode^.key, searchRec.key); if (res > 0) then break; inc(dataNode, align(keySize(@dataNode^.key))); inc(curNode); until curNode = node^.numKeys; if (res <= 0) and (curNode = node^.numKeys) then begin nextNode := node^.right; discardPage(node); node := loadPage(nextNode); if (node = NIL) then begin findClose(searchRec); exit end; {node = NIL} dataNode := @node^.data; end; FreeMem(searchRec.key, align(searchRec.keySize)); searchRec.value := dataNode^.link; searchRec.keySize := keySize(@dataNode^.key); searchRec.key := NIL; GetMem(searchRec.key, align(searchRec.keySize)); // sanity check if (searchRec.key <> NIL) then begin fillchar(searchRec.key^, align(searchRec.keySize), 0); copyKey(@dataNode^.key, searchRec.key); result := TRUE end; discardPage(node); end; {bTree.FindNext} function bTree.FindPrev; var prevNode:uPtr; node:PbNode; lastDataNode, dataNode:PbNodeData; curNode, res:integer; begin result := FALSE; if (searchRec.key = NIL) then exit; node := loadPage(findLeafNode(getRoot(), searchRec.key)); if (node = NIL) then begin findClose(searchRec); exit end; dataNode := @node^.data; lastDataNode := dataNode; curNode := 0; res := compareKeys(searchRec.key, @dataNode^.key); if (res > 0) then repeat lastDataNode := dataNode; inc(curNode); inc(dataNode, align(keySize(@dataNode^.key))); res := compareKeys(searchRec.key, @dataNode^.key); until (res <= 0) or (curNode = node^.numKeys); dataNode := lastDataNode; if ((res <= 0) and (curNode = 0)) then begin prevNode := node^.left; discardPage(node); node := loadPage(prevNode); if (node = NIL) then begin FindClose(searchRec); exit end; dataNode := @node^.data; // scan to the end of the previous node. // If there's only 1 key, this will do nothing for curNode := 1 to node^.numKeys-1 do inc(dataNode, align(keySize(@dataNode^.key))); end; FreeMem(searchRec.key, align(searchRec.keySize)); searchRec.value := dataNode^.link; searchRec.keySize := keySize(@dataNode^.key); searchRec.key := NIL; GetMem(searchRec.key, align(searchRec.keySize)); // sanity check if (searchRec.key <> NIL) then begin fillchar(searchRec.key^, align(searchRec.keySize), 0); copyKey(@dataNode^.key, searchRec.key); result := TRUE end; discardPage(node) end; {bTree.FindPrev} function bTree.GetFirstKey; begin with searchRec do begin setNull(value); key := NIL; keySize := 0; end; {with searchRec} result := fetchFirstkey(getRoot(), searchRec); end; {bTree.GetFirstKey} function bTree.GetKeyCount; begin if (info <> NIL) then result := info^.keys else result := 0 end; // bTree.GetKeyCount function bTree.GetLastKey; begin with searchRec do begin setNull(value); key := NIL; keySize := 0; end; {with searchRec} result := fetchLastKey(getRoot(), searchRec); end; {bTree.GetLastKey} procedure bTree.Print; var curNode:integer; dataNode:PbNodeData; node : PbNode; link : uPtr; begin if isNull(u) then exit; PrintInfo(f, u); node := loadPage(u); curNode := 0; if (node^.magic = B_NODE_INDEX) then begin dataNode := @node^.data; for curNode := 0 to node^.numKeys-1 do begin Print(f, dataNode^.link); inc(dataNode, align(keySize(@dataNode^.key))); end; Print(f, dataNode^.link); end; discardPage(node); end; {bTree.Print} procedure bTree.PrintInfo; const leafStr:array[boolean] of string[5] = ('INDEX', 'LEAF'); var dataNode:PbNodeData; curNode:integer; node : PbNode; i:integer; begin node := loadPage(u); if (node = NIL) then exit; write(f, ' ', hex(dword(node^.parent.offset))); writeln(f, ' ', leafStr[node^.magic = B_NODE_LEAF], ' (', hex(node^.magic), ')'); write(f, hex(dword(node^.left.bPtr)), ' <-> ', hex(getAddress(node).u), ' <-> ', hex(dword(node^.right.bPtr))); writeln(f, ' | capacity ', node^.size, '[', calcSize(node), '](', info^.bNodeSize, ') | keys:', node^.numKeys); if (node^.numKeys = 0) then exit; dataNode := @node^.data; curNode := 0; for curNode := 0 to node^.numKeys-(ord(node^.magic = B_NODE_LEAF)) do begin write(f, '| ', curNode:2, '| '); {if (node^.magic = B_NODE_INDEX) then} write(f, '[', hex(dword(dataNode^.link.offset)), ']'); if (curNode <> node^.numKeys) then case info^.treeType of BT_custom:with TBlockRun(dataNode^.key) do writeln(f, ' {', AG, ':', start, ':', len, '} '); BT_INT32:writeln(f, ' {', hex(dataNode^.key.i), '} '); BT_INT64:writeln(f, ' {', hex(dataNode^.key.x), '} '); BT_SINGLE:writeln(f, ' {', dataNode^.key.s, '} '); BT_DOUBLE:writeln(f, ' {', dataNode^.key.d, '} '); BT_STRING:writeln(f, ' {', dataNode^.key.str, '} '); BT_PCHAR:begin (*writeln(f, ' {', pchar(@dataNode^.key)^, '} '); *) write(f, ' {'); i:=0; while (dataNode^.key.str[i]<>#0) do begin write(f, dataNode^.key.str[i]); inc(i); end; writeln(f, '} '); end; end; inc(dataNode, align(keySize(@dataNode^.key))); end; discardPage(node); writeln(f); end; {bTree.PrintInfo} function bTree.Insert; begin result := insertEntry(findLeafNode(getRoot(), key), key, value); end; {bTree.Insert} procedure bTree.InstallUserFunctions; begin compareKeys := cmpFunc; copyKey := copyProc; keySize := ksFunc; end; {bTree.InstallUserFunc} procedure bTree.PrintList; var dataNode:PbNodeData; node:PbNode; link:uPtr; i, j : integer; f:text; begin if (isNull(getRoot())) then exit; assign(f, filename); {$I-} rewrite(f); {$I+} if (IOResult() <> 0) then exit; node := loadPage(getRoot()); if (node = NIL) then exit; while (node^.magic = B_NODE_INDEX) do begin dataNode := @node^.data; link := dataNode^.link; discardPage(node); node := loadPage(link); end; while (not isNull(link)) do begin dataNode := @node^.data; for i := 0 to node^.numKeys -1 do begin case info^.treeType of BT_CUSTOM:with TBlockRun(dataNode^.key) do writeln(f, AG, ':', start, ':', len); BT_INT32:writeln(f, hex(dataNode^.key.i)); // BT_INT64:writeln(f, dataNode^.key.x); BT_SINGLE:writeln(f, dataNode^.key.s); BT_DOUBLE:writeln(f, dataNode^.key.d); BT_STRING:writeln(f, dataNode^.key.str); BT_PCHAR:begin j:=0; while (dataNode^.key.str[j]<>#0) do begin write(f, dataNode^.key.str[j]); inc(j); end; writeln(f); end; end; // case inc(dataNode, align(keySize(@dataNode^.key))); end; {for i} link := node^.right; discardPage(node); node := loadPage(link); end; {while} {$I-} close(f); {$I+} if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintList'); end; {bTree.PrintList} procedure bTree.PrintWholeTree; var f:text; begin assign(f, filename); {$I-} rewrite(f); {$I+} if (IOResult() <> 0) then exit; Print(f, getRoot()); {$I-} close(f); {$I+} if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintWholeTree'); end; {bTree.PrintWholeTree} destructor bTree.done; begin if (not(memoryTree) and treeChanged) then begin // if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()'); // if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()'); if not vfs^.fSeek(0) then writeln('Error seeking in bTree.done()'); // reset back to the beginning if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.done()'); if not vfs^.fClose() then writeln('Error calling fClose file in bTree.done()'); // disposing the vfs pointer will close the file as necessary end; if (not isNull(getRoot())) then freeAll; { with info^ do begin writeln('tree height: ', height); writeln('tree leaf key count: ', keys); writeln('Total number of nodes: ', nodes); // freeAll; end; } dispose(info); if (vfs <> NIL) then dispose(vfs, done); info := NIL; end; // bTree.done constructor TQueue.init; begin queue := NIL; elements := 0; maxElements := elementCount; size := elementSize; head := 0; tail := 0; if (size <> 0) and (maxElements <> 0) then begin GetMem(queue, size*maxElements); fillchar(queue^, size*maxElements, 0); end; end; // TQueue.init function TQueue.dequeue; var p:pointer; begin result := FALSE; if (queue = NIL) or (elements = 0) then exit; p := queue; inc(p, head*size); head := (head+1) mod maxElements; move(p^, item, size); dec(elements); result := TRUE; end; // TQueue.dequeue function TQueue.enqueue; var p:pointer; begin result := FALSE; if (queue = NIL) or (elements = maxElements) then exit; p := queue; inc(p, tail*size); move(item, p^, size); tail := (tail+1) mod maxElements; inc(elements); result := TRUE; end; // TQueue.enqueue function TQueue.getElementCount; result := elements; destructor TQueue.done; begin if (size <> 0) and (maxElements <> 0) then FreeMem(queue, size*maxElements); queue := NIL; elements := 0; maxElements := 0; size := 0; head := 0; tail := 0; end; // TQueue.done end.