{*********************************************************} {* FlashFiler: Table b-tree index access *} {*********************************************************} (* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is TurboPower FlashFiler * * The Initial Developer of the Original Code is * TurboPower Software * * Portions created by the Initial Developer are Copyright (C) 1996-2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** *) {$I ffdefine.inc} unit fftbindx; interface uses SysUtils, ffconst, ffllbase, ffsrmgr, ffllexcp, ffsrintf, ffsrbase, fffile, ffsrlock, fftbbase, fftbdict; {$IFDEF FF_DEBUG} var FFDEBUG_IndexCounter : record Splits, RotateLeftNode, RotateRightNode, RotateLeftLeaf, RotateRightLeaf, Merge, SwapNext, SwapPrev : integer; end; {$ENDIF} type PffKeyIndexData = ^TffKeyIndexData; TffKeyIndexData = record {Data record for key routines} {must be supplied} kidFI : PffFileInfo; {..index file} kidIndex : integer; {..index number} kidCompare : TffKeyCompareFunc; {..compare routine} kidCompareData: PffCompareData; {..compare data} {calculated internally} kidFileHeader : PffBlockHeaderFile; {..pointer to the index file header} kidIndexHeader: PffIndexHeader; {..pointer to the index header} {used elsewhere} kidIndexType : TffIndexType; {..index type: composite or user-defined} end; type {Note: an key path is a structure which defines a particular key in a B-Tree. The path defines the page numbers and the element numbers into the key arrays in the pages to get to a particular key. If the position is OnKey then the keypath points exactly to that key. If the position is OnCrack then the keypath points to the key that would be retrieved by a NextKey or a PrevKey operation. An invalid keypath has an path element count of zero and a position of Unknown} TffKeyPathPosition = ( {Possible positions of a keypath..} kppUnknown, {..unknown} kppBOF, {..before all keys} kppOnCrackBefore, {..in between two keys} kppOnCrackAfter, {..in between two keys} kppOnKey, {..on a key} kppEOF); {..after all keys} TffKeyPathElement = record {An element of a key path} kpePage : TffWord32; {..the page number} kpeItem : integer; {..the element number of the key} end; PffKeyPath = ^TffKeyPath; TffKeyPath = record {The key path type} kpCount : integer; {..number of active elements in the path} kpPath : array [0..31] of TffKeyPathElement; {..the path} kpPos : TffKeyPathPosition; {..it's position} kpLSN : TffWord32; {...LSN of the index map page at the time we positioned to this record. If the LSN has changed then our key path is no longer valid. } end; {Note: 32 elements is *ample*, 32 levels in a sparsely populated order 3 B-Tree would hold 4 billion keys: anyone who creates such a B-Tree (ie 1Kb keys using a 4Kb block) deserve what they get if they even could.} {---Key path related routines---} procedure FFInitKeyPath(var aKeyPath : TffKeyPath); procedure FFSetKeyPathToBOF(var aKeyPath : TffKeyPath); procedure FFSetKeyPathToEOF(var aKeyPath : TffKeyPath); {---Index related routines--- (NOT THREAD-SAFE)} procedure FFTblAddIndex(aFI : PffFileInfo; aTI : PffTransInfo; aIndex : integer; aMaxKeyLen : integer; aAllowDups : boolean; aKeysAreRefs : boolean); {-Add an index to a file} procedure FFTblDeleteIndex(aFI : PffFileInfo; aTI : PffTransInfo; aIndex : integer); {-Delete an index from a file} procedure FFTblPrepareIndexes(aFI : PffFileInfo; aTI : PffTransInfo); {-Prepare a file to contain indexes} {---Key access related routines--- (NOT THREAD-SAFE)} procedure FFTblDeleteAllKeys(aTI : PffTransInfo; var aIndex : TffKeyIndexData); {-Delete all keys from an index. Note: cannot be used inside a transaction--it implements a low level file update} function FFTblDeleteKey(const aTI : PffTransInfo; const aKey : PffByteArray; const aRefNr : TffInt64; var aIndex : TffKeyIndexData; var aBTreeChanged : Boolean) : Boolean; {!!.05} {-Delete a key/ref from an index} function FFTblFindKey(var aIndex : TffKeyIndexData; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath; aAction : TffSearchKeyAction) : boolean; {-Find the given key/ref (or nearby one) in the index, set up the keypath and the key/ref found, return true; if key/ref not found, return false and an invalid keypath. Note that if the index allows dups and the refnr is zero and the key matches, the first matching key/ref is returned. Note also the keypath is positioned on the crack for the key/ref in question.} function FFTblGetApproxPos(var aIndex : TffKeyIndexData; var aPos : integer; aTI : PffTransInfo; const aKeyPath : TffKeyPath) : boolean; {-Given a valid keypath to key/ref, calculate the approx position of that key/ref in the b-tree as percentage.} function FFTblInsertKey(var aIndex : TffKeyIndexData; const aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray) : boolean; {-Insert a key/ref into an index} function FFTblKeyExists(var aIndex : TffKeyIndexData; const aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray) : boolean; {-Return true if key/ref exists in index. If the lock duration is ffldShort then index locks are released once this method has finished using the index pages. } function FFTblNextKey(var aIndex : TffKeyIndexData; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath) : boolean; {-Given a keypath, find the next key/ref in the index, set up the keypath and key/ref to it, return true; if no next key, return false and set keypath to EOF} function FFTblPrevKey(var aIndex : TffKeyIndexData; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath) : boolean; {-Given a keypath, find the previous key/ref in the index, set up the keypath and key/ref to it, return true; if no previous key, return false and set keypath to BOF} function FFTblSetApproxPos(var aIndex : TffKeyIndexData; aPos : integer; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath) : boolean; {-Set the keypath to the approximate position given by aPos (a percentage), return true and the key/ref if able to, return false if not. The returned keypath will have length 2, unless the b-tree only consists of the root page, in which case it will be length 1. Note: All index pages accessed by this method are Share locked for duration ffldCommit.} implementation {Notes: to optimize disk space, there are four types of btree pages: A: standard keys, node page B: standard keys, leaf page C: record reference number keys, node page D: record reference number keys, leaf page They have a dynamic (ie not static) format that depends on the block size of the file and the length of the keys. Ignoring the block header, in hand-waving terms the format of each block is as follows: A: an array of page numbers (32 bits, 4 bytes each), followed by an array of reference numbers (64 bits, 8 bytes each), followed by an array of keys (variable length). All arrays have the same number of elements. The "page before all keys" number is stored in the block header. B: an array of reference numbers (64 bits, 8 bytes each), followed by an array of keys (variable length). Both arrays have the same number of elements. C: an array of page numbers (32 bits, 4 bytes each), followed by an array of reference numbers (64 bits, 8 bytes each). Both arrays have the same number of elements. The "page before all keys" number is stored in the block header. The reference numbers are the keys. D: an array of reference numbers (64 bits, 8 bytes each). The reference numbers are the keys. The number of elements in the arrays MUST be odd because of the btree algorithm used. To calculate the number of elements in each array (the example values shown refer to a 4KB block with key length 20): A: take the block size, subtract the size of the header, divide by [sizeof(page number) + sizeof(ref number) + key length]. If not odd, subtract 1. Example: (4096 - 32) / (4 + 8 + 20) = 127 B: take the block size, subtract the size of the header, divide by [sizeof(ref number) + key length]. If not odd, subtract 1. Example: (4096 - 32) / (8 + 20) = 145 C: take the block size, subtract the size of the header, divide by [sizeof(page number) + sizeof(ref number)]. If not odd, subtract 1. Example: (4096 - 32) / (4 + 8) = 338; minus 1 = 337 D: take the block size, subtract the size of the header, divide by sizeof(ref number). If not odd, subtract 1. Example: (4096 - 32) / 8 = 508; minus 1 = 507 References: File Structures, Zoellick & Folk, Addison Wesley Introduction to Algorithms, Cormen, etc., McGraw-Hill Data Structures, Algorithms, and Performance, Wood, Addison Wesley Although the algorithm used here can be found in the above references, the data structures used are original.} type PRef = ^TRef; TRef = TffInt64; PPageNum = ^TpageNum; TPageNum = TffWord32; const SizeOfRef = sizeof(TRef); SizeOfPageNum = sizeof(TPageNum); type PRefBlock = ^TRefBlock; TRefBlock = array [0..($FFFFFFF div SizeOfRef)-1] of TRef; PPageNumBlock = ^TPageNumBlock; TPageNumBlock = array [0..($FFFFFFF div SizeOfPageNum)-1] of TPageNum; {===Helper routines==================================================} function GetNewInxHeaderBlock(aFI : PffFileInfo; aTI : PffTransInfo; var aReleaseMethod : TffReleaseMethod ) : PffBlock; {-Return a new index header block, pre-mark as dirty} var InxBlockHdr : PffBlockHeaderIndex absolute Result; InxHeader : PffIndexHeader; begin Result := FFTblHlpGetNewBlock(aFI, aTI, aReleaseMethod); with InxBlockHdr^ do begin bhiSignature := ffc_SigIndexBlock; bhiNextBlock := ffc_W32NoValue; bhiLSN := 0; bhiBlockType := ffc_InxBlockTypeHeader; bhiIsLeafPage := false; {not used in header} bhiNodeLevel := 0; {not used in header} bhiKeysAreRefs := false; {not used in header} bhiIndexNum := $FFFF; {not used in header} bhiKeyLength := 0; {not used in header} bhiKeyCount := 0; {not used in header} bhiMaxKeyCount := 0; {not used in header} bhiPrevPageRef := ffc_W32NoValue; {not used in header} end; InxHeader := PffIndexHeader(@Result^[ffc_BlockHeaderSizeIndex]); FillChar(InxHeader^, sizeof(TffIndexHeader), 0); end; {--------} function GetNewInxBtreeBlock(aFI : PffFileInfo; aTI : PffTransInfo; aIndexHeader : PffIndexHeader; aIndex : integer; aIsLeaf : boolean; var aReleaseMethod : TffReleaseMethod) : PffBlock; {-Return a new index btree node/leaf block, pre-mark as dirty} var InxBlockHdr : PffBlockHeaderIndex absolute Result; begin Result := FFTblHlpGetNewBlock(aFI, aTI, aReleaseMethod); with InxBlockHdr^, aIndexHeader^ do begin bhiSignature := ffc_SigIndexBlock; bhiNextBlock := ffc_W32NoValue; bhiBlockType := ffc_InxBlockTypeBtreePage; bhiIsLeafPage := aIsLeaf; if aIsLeaf then bhiNodeLevel := 1 {leaves are at level 1} else bhiNodeLevel := 0; {ie haven't a clue at present} bhiKeysAreRefs := (bihIndexFlags[aIndex] and ffc_InxFlagKeysAreRefs) <> 0; bhiIndexNum := aIndex; bhiKeyLength := bihIndexKeyLen[aIndex]; bhiKeyCount := 0; if aIsLeaf then if bhiKeysAreRefs then bhiMaxKeyCount := (aFI^.fiBlockSize - ffc_BlockHeaderSizeIndex) div (SizeOfRef) else bhiMaxKeyCount := (aFI^.fiBlockSize - ffc_BlockHeaderSizeIndex) div (bhiKeyLength + SizeOfRef) else {it's a node} if bhiKeysAreRefs then bhiMaxKeyCount := (aFI^.fiBlockSize - ffc_BlockHeaderSizeIndex) div (SizeOfPageNum + SizeOfRef) else bhiMaxKeyCount := (aFI^.fiBlockSize - ffc_BlockHeaderSizeIndex) div (bhiKeyLength + SizeOfPageNum + SizeOfRef); if not Odd(bhiMaxKeyCount) then dec(bhiMaxKeyCount); bhiPrevPageRef := ffc_W32NoValue; inc(bihIndexPageCount[aIndex]); end; end; {--------} function ReadVfyInxBlock(aFI : PffFileInfo; aTI : PffTransInfo; aFileHeader : PffBlockHeaderFile; const aMarkDirty : boolean; const aBlockType : integer; const aBlockNumber : TffWord32; var aReleaseMethod : TffReleaseMethod) : PffBlock; var InxBlockHdr : PffBlockHeaderIndex absolute Result; begin with aFileHeader^ do begin {verify the block number} if (aBlockNumber <= 0) or (aBlockNumber >= bhfUsedBlocks) then FFRaiseException(EffServerException, ffStrResServer, fferrBadBlockNr, [aFI^.fiName^, aBlockNumber]); {now get the record block; note: mark header block as fixed} Result := FFBMGetBlock(aFI, aTI, aBlockNumber, aMarkDirty, aReleaseMethod); {verify that it's an index block} if (InxBlockHdr^.bhiSignature <> ffc_SigIndexBlock) or (InxBlockHdr^.bhiThisBlock <> aBlockNumber) or (InxBlockHdr^.bhiBlockType <> aBlockType) then FFRaiseException(EffServerException, ffStrResServer, fferrBadInxBlock, [aFI^.fiName^, aBlockNumber]); end; end; {====================================================================} {===Key rotation routines============================================} procedure RotateLeftLeaf(aParentPage : PffBlock; aSeparator : Longint; aChildLeft : PffBlock; aChildRight : PffBlock); {-Rotate keys from right leaf child to left leaf child through key in parent given by separator index. Equalise number of keys} var ParentPageHdr : PffBlockHeaderIndex absolute aParentPage; ChildLeftHdr : PffBlockHeaderIndex absolute aChildLeft; ChildRightHdr : PffBlockHeaderIndex absolute aChildRight; KeysToMove : Longint; OffsetL : Longint; OffsetR : Longint; OffsetP : Longint; BytesToMove : Longint; begin {Assumptions: all relevant pages have been marked dirty} {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.RotateLeftLeaf); {$ENDIF} {calculate the number of keys to move, this means that the right child will *lose* this number of keys and the left child will *gain* this number} KeysToMove := (ChildRightHdr^.bhiKeyCount - ChildLeftHdr^.bhiKeyCount) div 2; if (KeysToMove = 0) then inc(KeysToMove); {move the first pred(KeysToMove) keys from the right child to the last pred(KeysToMove) places of the left child, the last key of all comes from/goes to the parent} with ChildLeftHdr^ do begin {move the reference numbers} OffsetL := ffc_BlockHeaderSizeIndex + (bhiKeyCount * SizeOfRef); OffsetR := ffc_BlockHeaderSizeIndex; OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * SizeOfPageNum) + (aSeparator * SizeOfRef); {..move parent ref} PRef(@aChildLeft^[OffsetL])^ := PRef(@aParentPage^[OffsetP])^; {..move first set of refs} BytesToMove := pred(KeysToMove) * SizeOfRef; Move(aChildRight^[OffsetR], aChildLeft^[OffsetL + SizeOfRef], BytesToMove); {..set parent ref} PRef(@aParentPage^[OffsetP])^ := PRef(@aChildRight^[OffsetR + BytesToMove])^; {..close up the gap} BytesToMove := (ChildRightHdr^.bhiKeyCount - KeysToMove) * SizeOfRef; Move(aChildRight^[OffsetR + (KeysToMove * SizeOfRef)], aChildRight^[OffsetR], BytesToMove); {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin {move the keys} OffsetL := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef) + (bhiKeyCount * bhiKeyLength); OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef); OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)) + (aSeparator * bhiKeyLength); {..move parent key} Move(aParentPage^[OffsetP], aChildLeft^[OffsetL], bhiKeyLength); {..move first set of keys} BytesToMove := pred(KeysToMove) * bhiKeyLength; Move(aChildRight^[OffsetR], aChildLeft^[OffsetL + bhiKeyLength], BytesToMove); {..set parent key} Move(aChildRight^[OffsetR + BytesToMove], aParentPage^[OffsetP], bhiKeyLength); {..close up the gap} BytesToMove := (ChildRightHdr^.bhiKeyCount - KeysToMove) * bhiKeyLength; Move(aChildRight^[OffsetR + (KeysToMove * bhiKeyLength)], aChildRight^[OffsetR], BytesToMove); end; end; {Update the key counts} inc(ChildLeftHdr^.bhiKeyCount, KeysToMove); dec(ChildRightHdr^.bhiKeyCount, KeysToMove); end; {--------} procedure RotateLeftNode(aParentPage : PffBlock; aSeparator : Longint; aChildLeft : PffBlock; aChildRight : PffBlock); {-Rotate keys from right node child to left node child through key in parent given by separator index. Equalise number of keys} var ParentPageHdr : PffBlockHeaderIndex absolute aParentPage; ChildLeftHdr : PffBlockHeaderIndex absolute aChildLeft; ChildRightHdr : PffBlockHeaderIndex absolute aChildRight; KeysToMove : Longint; OffsetL : Longint; OffsetR : Longint; OffsetP : Longint; BytesToMove : Longint; begin {Assumptions: all relevant pages have been marked dirty} {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.RotateLeftNode); {$ENDIF} {calculate the number of keys to move, this means that the right child will *lose* this number of keys and the left child will *gain* this number} KeysToMove := (ChildRightHdr^.bhiKeyCount - ChildLeftHdr^.bhiKeyCount) div 2; if (KeysToMove = 0) then inc(KeysToMove); {move the first pred(KeysToMove) keys from the right child to the last pred(KeysToMove) places of the left child, the last key of all comes from/goes to the parent} with ChildLeftHdr^ do begin {move the page numbers} OffsetL := ffc_BlockHeaderSizeIndex + (bhiKeyCount * SizeOfPageNum); OffsetR := ffc_BlockHeaderSizeIndex; {..move set of page numbers} BytesToMove := KeysToMove * SizeOfPageNum; Move(aChildRight^[OffsetR - SizeOfPageNum], aChildLeft^[OffsetL], BytesToMove); {..close up the gap} BytesToMove := succ(ChildRightHdr^.bhiKeyCount - KeysToMove) * SizeOfPageNum; Move(aChildRight^[OffsetR + (pred(KeysToMove) * SizeOfPageNum)], aChildRight^[OffsetR - SizeOfPageNum], BytesToMove); {move the data reference numbers} OffsetL := ffc_BlockHeaderSizeIndex + ((bhiMaxKeyCount * SizeOfPageNum) + (bhiKeyCount * SizeOfRef)); OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum); OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * SizeOfPageNum) + (aSeparator * SizeOfRef); {..move parent ref} PRef(@aChildLeft^[OffsetL])^ := PRef(@aParentPage^[OffsetP])^; {..move first set of refs} BytesToMove := pred(KeysToMove) * SizeOfRef; Move(aChildRight^[OffsetR], aChildLeft^[OffsetL + SizeOfRef], BytesToMove); {..set parent ref} PRef(@aParentPage^[OffsetP])^ := PRef(@aChildRight^[OffsetR + BytesToMove])^; {..close up the gap} BytesToMove := (ChildRightHdr^.bhiKeyCount - KeysToMove) * SizeOfRef; Move(aChildRight^[OffsetR + (KeysToMove * SizeOfRef)], aChildRight^[OffsetR], BytesToMove); {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin {move the keys} OffsetL := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef) + (bhiKeyCount * bhiKeyLength)); OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)); OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * (SizeOfRef + SizeOfPageNum)) + (aSeparator * bhiKeyLength); {..move parent key} Move(aParentPage^[OffsetP], aChildLeft^[OffsetL], bhiKeyLength); {..move first set of keys} BytesToMove := pred(KeysToMove) * bhiKeyLength; Move(aChildRight^[OffsetR], aChildLeft^[OffsetL + bhiKeyLength], BytesToMove); {..set parent key} Move(aChildRight^[OffsetR + BytesToMove], aParentPage^[OffsetP], bhiKeyLength); {..close up the gap} BytesToMove := (ChildRightHdr^.bhiKeyCount - KeysToMove) * bhiKeyLength; Move(aChildRight^[OffsetR + (KeysToMove * bhiKeyLength)], aChildRight^[OffsetR], BytesToMove); end; end; {Update the key counts} inc(ChildLeftHdr^.bhiKeyCount, KeysToMove); dec(ChildRightHdr^.bhiKeyCount, KeysToMove); end; {--------} procedure RotateRightLeaf(aParentPage : PffBlock; aSeparator : Longint; aChildLeft : PffBlock; aChildRight : PffBlock); {-Rotate keys from left leaf child to right leaf child through key in parent given by separator index. Equalise number of keys} var ParentPageHdr : PffBlockHeaderIndex absolute aParentPage; ChildLeftHdr : PffBlockHeaderIndex absolute aChildLeft; ChildRightHdr : PffBlockHeaderIndex absolute aChildRight; KeysToMove : Longint; OffsetL : Longint; OffsetR : Longint; OffsetP : Longint; BytesToMove : Longint; begin {Assumptions: all relevant pages have been marked dirty} {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.RotateRightLeaf); {$ENDIF} {calculate the number of keys to move, this means that the left child will *lose* this number of keys and the right child will *gain* this number} KeysToMove := (ChildLeftHdr^.bhiKeyCount - ChildRightHdr^.bhiKeyCount) div 2; if (KeysToMove = 0) then inc(KeysToMove); {open up enough room in the right child for these keys, and move the last pred(KeysToMove) keys from the left child to the first pred(KeysToMove) places, the last key of all comes from/goes to the parent} with ChildRightHdr^ do begin {move the reference numbers} OffsetR := ffc_BlockHeaderSizeIndex; OffsetL := ffc_BlockHeaderSizeIndex + (ChildLeftHdr^.bhiKeyCount - KeysToMove) * SizeOfRef; OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * SizeOfPageNum) + (aSeparator * SizeOfRef); {..open up space} BytesToMove := bhiKeyCount * SizeOfRef; Move(aChildRight^[OffsetR], aChildRight^[OffsetR + (KeysToMove * SizeOfRef)], BytesToMove); {..move last set of refs} BytesToMove := pred(KeysToMove) * SizeOfRef; Move(aChildLeft^[OffsetL + SizeOfRef], aChildRight^[OffsetR], BytesToMove); {..move parent ref} PRef(@aChildRight^[OffsetR + BytesToMove])^ := PRef(@aParentPage^[OffsetP])^; {..move to parent ref} PRef(@aParentPage^[OffsetP])^ := PRef(@aChildLeft^[OffsetL])^; {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin {move the keys} OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef); OffsetL := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef) + (ChildLeftHdr^.bhiKeyCount - KeysToMove) * bhiKeyLength; OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)) + (aSeparator * bhiKeyLength); {..open up space} BytesToMove := bhiKeyCount * bhiKeyLength; Move(aChildRight^[OffsetR], aChildRight^[OffsetR + (KeysToMove * bhiKeyLength)], BytesToMove); {..move last set of keys} BytesToMove := pred(KeysToMove) * bhiKeyLength; {Start !!.01} // if BytesToMove > 0 then begin Move(aChildLeft^[OffsetL + bhiKeyLength], aChildRight^[OffsetR], BytesToMove); {..move parent key} Move(aParentPage^[OffsetP], aChildRight^[OffsetR + BytesToMove], bhiKeyLength); // end; {End !!.01} {..move to parent key} Move(aChildLeft^[OffsetL], aParentPage^[OffsetP], bhiKeyLength); end; end; {Update the key counts} dec(ChildLeftHdr^.bhiKeyCount, KeysToMove); inc(ChildRightHdr^.bhiKeyCount, KeysToMove); end; {--------} procedure RotateRightNode(aParentPage : PffBlock; aSeparator : Longint; aChildLeft : PffBlock; aChildRight : PffBlock); {-Rotate keys from left node child to right node child through key in parent given by separator index. Equalise number of keys} var ParentPageHdr : PffBlockHeaderIndex absolute aParentPage; ChildLeftHdr : PffBlockHeaderIndex absolute aChildLeft; ChildRightHdr : PffBlockHeaderIndex absolute aChildRight; KeysToMove : Longint; OffsetL : Longint; OffsetR : Longint; OffsetP : Longint; BytesToMove : Longint; begin {Assumptions: all relevant pages have been marked dirty} {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.RotateRightNode); {$ENDIF} {calculate the number of keys to move, this means that the left child will *lose* this number of keys and the right child will *gain* this number} KeysToMove := (ChildLeftHdr^.bhiKeyCount - ChildRightHdr^.bhiKeyCount) div 2; if (KeysToMove = 0) then inc(KeysToMove); {open up enough room in the right child for these keys, and move the last pred(KeysToMove) keys from the left child to the first pred(KeysToMove) places, the last key of all comes from/goes to the parent} with ChildRightHdr^ do begin {move the page numbers} OffsetR := ffc_BlockHeaderSizeIndex; OffsetL := ffc_BlockHeaderSizeIndex + (ChildLeftHdr^.bhiKeyCount - KeysToMove) * SizeOfPageNum; {..open up space} BytesToMove := succ(bhiKeyCount) * SizeOfPageNum; Move(aChildRight^[OffsetR - SizeOfPageNum], aChildRight^[OffsetR + (pred(KeysToMove) * SizeOfPageNum)], BytesToMove); {..move set of page numbers} BytesToMove := KeysToMove * SizeOfPageNum; Move(aChildLeft^[OffsetL], aChildRight^[OffsetR - SizeOfPageNum], BytesToMove); {move the data reference numbers} OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum); OffsetL := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum) + (ChildLeftHdr^.bhiKeyCount - KeysToMove) * SizeOfRef; OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * SizeOfPageNum) + (aSeparator * SizeOfRef); {..open up space} BytesToMove := bhiKeyCount * SizeOfRef; Move(aChildRight^[OffsetR], aChildRight^[OffsetR + (KeysToMove * SizeOfRef)], BytesToMove); {..move last set of refs} BytesToMove := pred(KeysToMove) * SizeOfRef; Move(aChildLeft^[OffsetL + SizeOfRef], aChildRight^[OffsetR], BytesToMove); {..move parent ref} PRef(@aChildRight^[OffsetR + BytesToMove])^ := PRef(@aParentPage^[OffsetP])^; {..move to parent ref} PRef(@aParentPage^[OffsetP])^ := PRef(@aChildLeft^[OffsetL])^; {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin {move the keys} OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)); OffsetL := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)) + (ChildLeftHdr^.bhiKeyCount - KeysToMove) * bhiKeyLength; OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)) + (aSeparator * bhiKeyLength); {..open up space} BytesToMove := bhiKeyCount * bhiKeyLength; Move(aChildRight^[OffsetR], aChildRight^[OffsetR + (KeysToMove * bhiKeyLength)], BytesToMove); {..move last set of keys} BytesToMove := pred(KeysToMove) * bhiKeyLength; Move(aChildLeft^[OffsetL + bhiKeyLength], aChildRight^[OffsetR], BytesToMove); {..move parent key} Move(aParentPage^[OffsetP], aChildRight^[OffsetR + BytesToMove], bhiKeyLength); {..move to parent key} Move(aChildLeft^[OffsetL], aParentPage^[OffsetP], bhiKeyLength); end; end; {Update the key counts} dec(ChildLeftHdr^.bhiKeyCount, KeysToMove); inc(ChildRightHdr^.bhiKeyCount, KeysToMove); end; {====================================================================} {===Key insertion into/deletion from/swapping pages==================} procedure InsertKeyInLeafPage(aLeaf : PffBlock; aElement : Longint; aKey : PffByteArray; const aRefNr : TffInt64); var LeafHeader: PffBlockHeaderIndex absolute aLeaf; RefBlock : PRefBlock; KeyBlock : PffByteArray; Offset : integer; begin {Assumptions: aLeaf has been marked dirty} with LeafHeader^ do begin {get the address of the reference block} RefBlock := PRefBlock(@aLeaf^[ffc_BlockHeaderSizeIndex]); {open up room to insert the new reference} Move(RefBlock^[aElement], RefBlock^[succ(aElement)], SizeOfRef * (bhiKeyCount - aElement)); {insert the new reference} RefBlock^[aElement] := aRefNr; {if keys are separate entities, insert into key block} if not LeafHeader^.bhiKeysAreRefs then begin {get the address of the keyblock} KeyBlock := PffByteArray(@aLeaf^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); {open up room to insert the new key} Offset := aElement * bhiKeyLength; Move(KeyBlock^[Offset], KeyBlock^[Offset + bhiKeyLength], bhiKeyLength * (bhiKeyCount - aElement)); {insert the new key} Move(aKey^, KeyBlock^[Offset], bhiKeyLength); end; {increment the number of keys} inc(bhiKeyCount); end; end; {--------} procedure InsertKeyInNodePage(aNode : PffBlock; aElement : Longint; aKey : PffByteArray; const aRefNr : TffInt64; aChild : TffWord32); var NodeHeader: PffBlockHeaderIndex absolute aNode; PageBlock : PPageNumBlock; RefBlock : PRefBlock; KeyBlock : PffByteArray; Offset : integer; begin {Assumptions: aNode has been marked dirty} with NodeHeader^ do begin {get the address of the page number block} PageBlock := PPageNumBlock(@aNode^[ffc_BlockHeaderSizeIndex]); {open up room to insert the new reference} Move(PageBlock^[aElement], PageBlock^[succ(aElement)], SizeOfPageNum * (bhiKeyCount - aElement)); {insert the new page number} PageBlock^[aElement] := aChild; {get the address of the data reference block} RefBlock := PRefBlock(@aNode^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); {open up room to insert the new reference} Move(RefBlock^[aElement], RefBlock^[succ(aElement)], SizeOfRef * (bhiKeyCount - aElement)); {insert the new reference} RefBlock^[aElement] := aRefNr; {if keys are separate entities, insert into key block} if not bhiKeysAreRefs then begin {get the address of the keyblock} KeyBlock := PffByteArray(@aNode^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); {open up room to insert the new key} Offset := aElement * bhiKeyLength; Move(KeyBlock^[Offset], KeyBlock^[Offset + bhiKeyLength], bhiKeyLength * (bhiKeyCount - aElement)); {insert the new key} Move(aKey^, KeyBlock^[Offset], bhiKeyLength); end; {increment the number of keys} inc(bhiKeyCount); end; end; {--------} procedure RemoveKeyFromLeafPage(aLeaf : PffBlock; aElement : Longint); var LeafHeader: PffBlockHeaderIndex absolute aLeaf; RefBlock : PRefBlock; KeyBlock : PffByteArray; Offset : integer; begin { Assumption: We have Exclusively locked aLeaf. } with LeafHeader^ do begin {decrement the key count} dec(bhiKeyCount); {get the address of the data reference block} RefBlock := PRefBlock(@aLeaf^[ffc_BlockHeaderSizeIndex]); {close up to delete the reference} Move(RefBlock^[succ(aElement)], RefBlock^[aElement], SizeOfRef * (bhiKeyCount - aElement)); {if keys are separate entities, delete from key block} if not LeafHeader^.bhiKeysAreRefs then begin {get the address of the key block} KeyBlock := PffByteArray(@aLeaf^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); {close up to delete the key} Offset := aElement * bhiKeyLength; Move(KeyBlock^[Offset+bhiKeyLength], KeyBlock^[Offset], bhiKeyLength * (bhiKeyCount - aElement)); end; end; end; {--------} procedure RemoveKeyFromNodePage(aNode : PffBlock; aElement : Longint); var NodeHeader: PffBlockHeaderIndex absolute aNode; PageBlock : PPageNumBlock; RefBlock : PRefBlock; KeyBlock : PffByteArray; Offset : integer; begin {Assumptions: aNode has been marked dirty} with NodeHeader^ do begin {decrement the key count} dec(bhiKeyCount); {get the address of the page number block} PageBlock := PPageNumBlock(@aNode^[ffc_BlockHeaderSizeIndex]); {close up to delete the page number} Move(PageBlock^[succ(aElement)], PageBlock^[aElement], SizeOfPageNum * (bhiKeyCount - aElement)); {get the address of the data reference block} RefBlock := PRefBlock(@aNode^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); {close up to delete the reference} Move(RefBlock^[succ(aElement)], RefBlock^[aElement], SizeOfRef * (bhiKeyCount - aElement)); {if keys are separate entities, delete from key block} if not bhiKeysAreRefs then begin {get the address of the key block} KeyBlock := PffByteArray(@aNode^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); {close up to delete the key} Offset := aElement * bhiKeyLength; Move(KeyBlock^[Offset+bhiKeyLength], KeyBlock^[Offset], bhiKeyLength * (bhiKeyCount - aElement)); end; end; end; {--------} procedure SwapKeys(aNode : PffBlock; aNElement : Longint; aLeaf : PffBlock; aLElement : Longint; aKey : PffByteArray); {-Swap the key at aNElement in aNode with that at aLElement in aLeaf} var NodeHdr : PffBlockHeaderIndex absolute aNode; LeafHdr : PffBlockHeaderIndex absolute aLeaf; OffsetN : Longint; OffsetL : Longint; Temp : TffInt64; begin {Assumptions: aNode, aLeaf have been marked dirty; the key at aNElement in aNode compares equal to aKey} with NodeHdr^ do begin {swap references} OffsetN := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum) + (aNElement * SizeOfRef); OffsetL := ffc_BlockHeaderSizeIndex + (aLElement * SizeOfRef); Temp := PRef(@aNode^[OffsetN])^; PRef(@aNode^[OffsetN])^ := PRef(@aLeaf^[OffsetL])^; PRef(@aLeaf^[OffsetL])^ := Temp; {if keys are separate entities, swap keys} if not bhiKeysAreRefs then begin OffsetN := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)) + (aNElement * NodeHdr^.bhiKeyLength); OffsetL := ffc_BlockHeaderSizeIndex + (LeafHdr^.bhiMaxKeyCount * SizeOfRef) + (aLElement * LeafHdr^.bhiKeyLength); Move(aLeaf^[OffsetL], aNode^[OffsetN], bhiKeyLength); Move(aKey^, aLeaf^[OffsetL], bhiKeyLength); end; end; end; {====================================================================} {===Page splitting/merging routines==================================} procedure MergeChildren(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aParentPage : PffBlock; aSeparator : Longint; aChildLeft : PffBlock; aChildRight : PffBlock); {-Merge the right child into the left child, separated by the given key from the parent. The right child is deleted; the parent loses one key.} var ParentPageHdr : PffBlockHeaderIndex absolute aParentPage; ChildLeftHdr : PffBlockHeaderIndex absolute aChildLeft; ChildRightHdr : PffBlockHeaderIndex absolute aChildRight; OffsetL : Longint; OffsetR : Longint; OffsetP : Longint; begin {Assumptions: all relevant pages have been marked dirty} {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.Merge); {$ENDIF} with ChildLeftHdr^ do begin {Note: this routine is *only* called if both children have (bhiMaxKeyCount div 2) keys--the absolute minimum} if (bhiKeyCount <> (bhiMaxKeyCount div 2)) or (bhiKeyCount <> ChildRightHdr^.bhiKeyCount) then FFRaiseException(EffServerException, ffStrResServer, fferrBadMergeCall, [aIndexData.kidFI^.fiName^, bhiThisBlock, ChildRightHdr^.bhiThisBlock]); {the merge process is different for nodes and leaves} if (not bhiIsLeafPage) then begin {copy over the page numbers} OffsetR := ffc_BlockHeaderSizeIndex; OffsetL := ffc_BlockHeaderSizeIndex + (bhiKeyCount * SizeOfPageNum); Move(aChildRight^[OffsetR - SizeOfPageNum], aChildLeft^[OffsetL], (succ(bhiKeyCount) * SizeOfPageNum)); {set up offsets for data references} OffsetL := ffc_BlockHeaderSizeIndex + ((bhiMaxKeyCount * SizeOfPageNum) + (bhiKeyCount * SizeOfRef)); OffsetR := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum); end else {it's a leaf} begin {set up offsets for data references} OffsetL := ffc_BlockHeaderSizeIndex + (bhiKeyCount * SizeOfRef); OffsetR := ffc_BlockHeaderSizeIndex; end; {copy over parent data reference} OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * SizeOfPageNum) + (aSeparator * SizeOfRef); PRef(@aChildLeft^[OffsetL])^ := PRef(@aParentPage^[OffsetP])^; {copy over other data references} Move(aChildRight^[OffsetR], aChildLeft^[OffsetL + SizeOfRef], (bhiKeyCount * SizeOfRef)); {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin {set up offsets for keys} inc(OffsetL, ((bhiMaxKeyCount-bhiKeyCount) * SizeOfRef) + (bhiKeyCount * bhiKeyLength)); inc(OffsetR, (bhiMaxKeyCount * SizeOfRef)); OffsetP := ffc_BlockHeaderSizeIndex + (ParentPageHdr^.bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)) + (aSeparator * bhiKeyLength); {copy over the parent key} Move(aParentPage^[OffsetP], aChildLeft^[OffsetL], bhiKeyLength); {copy over all the other keys} Move(aChildRight^[OffsetR], aChildLeft^[OffsetL + bhiKeyLength], (bhiKeyCount * bhiKeyLength)); end; {delete the parent key since it now points to an invalid page} RemoveKeyFromNodePage(aParentPage, aSeparator); {patch up the left child's key count} bhiKeyCount := bhiMaxKeyCount; end; {delete the right child, it is no longer referenced} with aIndexData do begin FFTblHlpDeleteBlock(kidFI, kidFileHeader, aChildRight); dec(kidIndexHeader^.bihIndexPageCount[kidIndex]); end; end; {--------} procedure BtreeSplitChild(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aParentPage : PffBlock; aChildIndex : integer; aChildPage : PffBlock); {-Split the given child into two children. If the number of keys in the child is 2N+1, each child will end up with N keys, the parent gains one key at aChildIndex.} var aParentPageHdr : PffBlockHeaderIndex absolute aParentPage; aChildPageHdr : PffBlockHeaderIndex absolute aChildPage; NewChildPage : PffBlock; NewChildPageHdr : PffBlockHeaderIndex absolute NewChildPage; NewChild : Longint; NewOffset : integer; OldOffset : integer; MedianRef : TRef; aRelMethod : TffReleaseMethod; begin { Assumptions: aParentPage and aChildPage have been marked dirty. } {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.Splits); {$ENDIF} aRelMethod := nil; with aChildPageHdr^ do begin {create a new child page} with aIndexData do NewChildPage := GetNewInxBtreeBlock(kidFI, aTI, kidIndexHeader, kidIndex, bhiIsLeafPage, aRelMethod); try NewChild := NewChildPageHdr^.bhiThisBlock; NewChildPageHdr^.bhiNodeLevel := bhiNodeLevel; {transfer the second half of the old child to the first half of the new one} {note this depends on whether the page is an internal node or a leaf} if (not bhiIsLeafPage) then begin {move the page numbers} {note: we must transfer into the prev page number field of the header} NewOffset := ffc_BlockHeaderSizeIndex - SizeOfPageNum; OldOffset := NewOffset + (succ(bhiMaxKeyCount div 2) * SizeOfPageNum); Move(aChildPage^[OldOffset], NewChildPage^[NewOffset], succ(bhiMaxKeyCount div 2) * SizeOfPageNum); {move the data references} NewOffset := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum) ; OldOffset := NewOffset + (succ(bhiMaxKeyCount div 2) * SizeOfRef); Move(aChildPage^[OldOffset], NewChildPage^[NewOffset], (bhiMaxKeyCount div 2) * SizeOfRef); MedianRef := PRef(@aChildPage^[OldOffset-SizeOfRef])^; {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin NewOffset := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef)); OldOffset := NewOffset + (succ(bhiMaxKeyCount div 2) * bhiKeyLength); Move(aChildPage^[OldOffset], NewChildPage^[NewOffset], (bhiMaxKeyCount div 2) * bhiKeyLength); end; end else {it's a leaf} begin {move the data references} NewOffset := ffc_BlockHeaderSizeIndex; OldOffset := NewOffset + (succ(bhiMaxKeyCount div 2) * SizeOfRef); Move(aChildPage^[OldOffset], NewChildPage^[NewOffset], (bhiMaxKeyCount div 2) * SizeOfRef); MedianRef := PRef(@aChildPage^[OldOffset-SizeOfRef])^; {if keys are separate entities, move the keys} if not bhiKeysAreRefs then begin NewOffset := ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef); OldOffset := NewOffset + (succ(bhiMaxKeyCount div 2) * bhiKeyLength); Move(aChildPage^[OldOffset], NewChildPage^[NewOffset], (bhiMaxKeyCount div 2) * bhiKeyLength); end; end; {insert the median key into the parent} InsertKeyInNodePage(aParentPage, aChildIndex, PffByteArray(@aChildPage^[OldOffset-bhiKeyLength]), MedianRef, NewChild); {set the number of keys in each child} bhiKeyCount := bhiMaxKeyCount div 2; NewChildPageHdr^.bhiKeyCount := bhiMaxKeyCount div 2; finally aRelMethod(NewChildPage); end; end; end; {====================================================================} {===Key insertion helper routines====================================} function BtreeInsertRedistribute(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aParentPage : PffBlock; aChildIndex : integer; aChildPage : PffBlock) : boolean; var aParentPageHdr : PffBlockHeaderIndex absolute aParentPage; SiblingPage : PffBlock; SiblingPageHdr : PffBlockHeaderIndex absolute SiblingPage; Sibling : Longint; PageBlock : PPageNumBlock; aRelMethod : TffReleaseMethod; begin { Assumption: aParentPage and aChildPage have been marked dirty. } Result := false; { Try the child's successor sibling page. } if (aChildIndex < aParentPageHdr^.bhiKeyCount) then begin PageBlock := PPageNumBlock(@aParentPage^[ffc_BlockHeaderSizeIndex]); Sibling := PageBlock^[aChildIndex]; with aIndexData do SiblingPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeBtreePage, Sibling, aRelMethod); try { Are there at least two spare key slots? } if (SiblingPageHdr^.bhiKeyCount < pred(SiblingPageHdr^.bhiMaxKeyCount)) then begin { Yes. Redistribute the keys. } if (not SiblingPageHdr^.bhiIsLeafPage) then RotateRightNode(aParentPage, aChildIndex, aChildPage, SiblingPage) else RotateRightLeaf(aParentPage, aChildIndex, aChildPage, SiblingPage); Result := true; end; finally aRelMethod(SiblingPage); end; end; { If not done it yet, try the child's predecessor sibling page. } if (not Result) and (aChildIndex > 0) then begin if (aChildIndex = 1) then Sibling := aParentPageHdr^.bhiPrevPageRef else begin PageBlock := PPageNumBlock(@aParentPage^[ffc_BlockHeaderSizeIndex]); Sibling := PageBlock^[aChildIndex - 2]; end; with aIndexData do SiblingPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeBtreePage, Sibling, aRelMethod); try { Are there at least two spare key slots? } if (SiblingPageHdr^.bhiKeyCount < pred(SiblingPageHdr^.bhiMaxKeyCount)) then begin { Yes. Redistribute the keys. } if (not SiblingPageHdr^.bhiIsLeafPage) then RotateLeftNode(aParentPage, aChildIndex-1, SiblingPage, aChildPage) else RotateLeftLeaf(aParentPage, aChildIndex-1, SiblingPage, aChildPage); Result := true; end; finally aRelMethod(SiblingPage); end; end; end; {--------} function BtreeInsertNonFull(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; var aPage : PffBlock; var aRelMethod : TffReleaseMethod; aKey : PffByteArray; const aRefNr : TffInt64) : boolean; var PageHdr : PffBlockHeaderIndex absolute aPage; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; L, R, M : integer; CompResult : integer; Child : Longint; ChildPage : PffBlock; ChildPageHdr : PffBlockHeaderIndex absolute ChildPage; Compare : TffKeyCompareFunc; AllowDups : boolean; DoneRecursing: boolean; aChildRelMethod : TffReleaseMethod; begin { Assumptions: aPage could be dirty or clean. Caller has incremented aPage's ref count. } Result := false; { Learn whether dup keys are allowed, get compare function. } with aIndexData do begin AllowDups := (kidIndexHeader^.bihIndexFlags[kidIndex] and ffc_InxFlagAllowDups) <> 0; Compare := kidCompare; end; {simulate recursion (ie unwind it} DoneRecursing := false; repeat with PageHdr^ do begin {get the addresses of the reference block and key string block, this is different for leaf and node pages} if bhiIsLeafPage then begin PageNumBlock := nil; DataRefBlock := PRefBlock(@aPage^[ffc_BlockHeaderSizeIndex]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@aPage^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); end else {its a node page} begin PageNumBlock := PPageNumBlock(@aPage^[ffc_BlockHeaderSizeIndex]); DataRefBlock := PRefBlock(@aPage^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount*SizeOfPageNum)]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@aPage^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); end; {binary search to find insertion point} L := 0; R := pred(bhiKeyCount); repeat M := (L + R) div 2; CompResult := Compare(aKey^, KeyBlock^[M * bhiKeyLength], aIndexData.kidCompareData); if (CompResult < 0) then R := pred(M) else if (CompResult > 0) then L := succ(M) else {CompResult = 0} if AllowDups then begin CompResult := FFCmpI64(aRefNr, DataRefBlock^[M]); if (CompResult < 0) then R := pred(M) else if (CompResult > 0) then L := succ(M) else {it's a duplicate key+refnr combo} Exit; end else {it's a duplicate key} Exit; until (L > R); if bhiIsLeafPage then begin { It's a leaf page. Mark it as dirty since we are about to modify it. } FFBMDirtyBlock(aIndexData.kidFI, bhiThisBlock, aTI, aPage); { The key+refnr combo doesn't exist, insert at L. } InsertKeyInLeafPage(aPage, L, aKey, aRefNr); Result := true; DoneRecursing := true; end else {it's a node page} begin { The child we need to traverse to is given by (L - 1). } if (L = 0) then Child := bhiPrevPageRef else Child := PageNumBlock^[pred(L)]; { Read the page. For now, we need a Share lock. } with aIndexData do ChildPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aChildRelMethod); { If this child is full, split it or redistribute now. } with ChildPageHdr^ do if (bhiKeyCount = bhiMaxKeyCount) then begin { Splitting a child/redistribution will update the parent page as well, so mark both pages as dirty. } FFBMDirtyBlock(aIndexData.kidFI, PageHdr^.bhiThisBlock, aTI, aPage); FFBMDirtyBlock(aIndexData.kidFI, Child, aTI, ChildPage); { Try redistribution else split the child. } if not BtreeInsertRedistribute(aIndexData, aTI, aPage, L, ChildPage) then BtreeSplitChild(aIndexData, aTI, aPage, L, ChildPage); aChildRelMethod(ChildPage); { We've just rearranged the keys in this page, recurse this page. } end else begin { Insert the key into the child's subtree, ie recurse with child. } aRelMethod(aPage); aRelMethod := aChildRelMethod; aPage := ChildPage; end; end; end; until DoneRecursing; end; {--------} function BtreeInsert(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aRoot : TffWord32; aKey : PffByteArray; const aRefNr : TffInt64) : boolean; var RootPage : PffBlock; RootPageHdr : PffBlockHeaderIndex absolute RootPage; NewRootPage : PffBlock; NewRootPageHdr : PffBlockHeaderIndex absolute NewRootPage; aNewRelMethod, aRelMethod : TffReleaseMethod; begin { Get the root page. } with aIndexData do RootPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeBtreePage, aRoot, aRelMethod); { If the root is full, we split it now. } with RootPageHdr^ do try if (bhiKeyCount = bhiMaxKeyCount) then begin { Since we're about to update it, mark the block as dirty. } FFBMDirtyBlock(aIndexData.kidFI, aRoot, aTI, RootPage); { Create a new root. } with aIndexData do NewRootPage := GetNewInxBtreeBlock(kidFI, aTI, kidIndexHeader, kidIndex, false, aNewRelMethod); try NewRootPageHdr^.bhiNodeLevel := succ(bhiNodeLevel); { Patch it so that the previous page is the old root. } NewRootPageHdr^.bhiPrevPageRef := aRoot; { Split the old root. } BtreeSplitChild(aIndexData, aTI, NewRootPage, 0, RootPage); { Update the index header to point to the new root. } with aIndexData do kidIndexHeader^.bihIndexRoot[kidIndex] := NewRootPageHdr^.bhiThisBlock; {now insert the key into the tree starting at the new root} Result := BtreeInsertNonFull(aIndexData, aTI, NewRootPage, aNewRelMethod, aKey, aRefNr); finally aNewRelMethod(NewRootPage); end; end else {insert the key into the tree starting at the root} Result := BtreeInsertNonFull(aIndexData, aTI, RootPage, aRelMethod, aKey, aRefNr); finally aRelMethod(RootPage); end; end; {====================================================================} {===Key deletion helper routines=====================================} procedure BtreeDeleteIndexPage(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aParent : TffWord32); var ParentPage : PffBlock; ParentPageHdr : PffBlockHeaderIndex absolute ParentPage; PageBlock : PPageNumBlock; Child : integer; aRelMethod : TffReleaseMethod; begin {WARNING: this is a recursive routine with an absolute maximum of 32 levels of recursion} {read the parent index page, mark dirty} with aIndexData do ParentPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeBtreePage, aParent, aRelMethod); try {get the page number block} PageBlock := PPageNumBlock(@ParentPage^[ffc_BlockHeaderSizeIndex]); with ParentPageHdr^ do {if there are children recurse through them all} if (bhiNodeLevel > 1) then begin BtreeDeleteIndexPage(aIndexData, aTI, bhiPrevPageRef); for Child := 0 to pred(bhiKeyCount) do BtreeDeleteIndexPage(aIndexData, aTI, PageBlock^[Child]); end; {delete this page} with aIndexData do begin FFTblHlpDeleteBlock(kidFI, kidFileHeader, ParentPage); end; finally aRelMethod(ParentPage); end; end; {--------} function BtreeDeleteSwapKey(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aParentPage : PffBlock; aKeyIndex : integer; aKey : PffByteArray; var aRelMethod : TffReleaseMethod) : PffBlock; var ParentPageHdr : PffBlockHeaderIndex absolute aParentPage; ChildPage : PffBlock; ChildPageHdr : PffBlockHeaderIndex absolute ChildPage; Child : TffWord32; LChildPage : PffBlock; LChildPageHdr : PffBlockHeaderIndex absolute LChildPage; LChild : TffWord32; RChildPage : PffBlock; RChildPageHdr : PffBlockHeaderIndex absolute RChildPage; RChild : TffWord32; ResultPageNum : TffWord32; MergeThem : boolean; aChildRelMeth, aLCRelMethod, aRCRelMethod : TffReleaseMethod; begin {Assumptions: aParentPage has already been marked dirty. The key at aKeyIndex in the parent equals aKey} { Assume that we shall have to do a merge. } LChild := 0; Result := nil; MergeThem := true; with ParentPageHdr^ do begin {we shall first search for the successor key} RChild := PPageNum(@aParentPage^[ffc_BlockHeaderSizeIndex + (aKeyIndex * SizeOfPageNum)])^; with aIndexData do RChildPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, RChild, aRCRelMethod); { Does this child page have enough keys?. } if (RChildPageHdr^.bhiKeyCount > (RChildPageHdr^.bhiMaxKeyCount div 2)) then begin {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.SwapNext); {$ENDIF} {save the right child page as Result} aRelMethod := aRCRelMethod; Result := RChildPage; ResultPageNum := RChild; {prepare to walk down the btree} Child := RChild; ChildPage := RChildPage; aChildRelMeth := aRCRelMethod; {continue walking down the btree going left all the time until we hit a leaf, locking pages as we go} while (not ChildPageHdr^.bhiIsLeafPage) do begin Child := ChildPageHdr^.bhiPrevPageRef; if ChildPage <> Result then {!!.01} aChildRelMeth(ChildPage); {!!.01} with aIndexData do ChildPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aChildRelMeth); end; { Mark the final leaf child since we shall be updating it. Note: If we walked down only 1 level then we need to make sure Result contains a pointer to the modified block put into ChildPage. } FFBMDirtyBlock(aIndexData.kidFI, Child, aTI, ChildPage); // if ResultPageNum = Child then begin {Deleted !!.01} // aRelMethod := aChildRelMeth; {Deleted !!.01} // Result := ChildPage; {Deleted !!.01} // end; {Deleted !!.01} { Swap the child's smallest key & ref with this key in the parent. } SwapKeys(aParentPage, aKeyIndex, ChildPage, 0, aKey); {Begin !!.01} { Did we modify the right child? } if Child = ResultPageNum then { Yes. Make sure we return the child page's modified block to the calling method. } Result := ChildPage else { No. Release the child page that we modified. } aChildRelMeth(ChildPage); {End !!.01} MergeThem := false; end; { If we couldn't use the successor, try the predecessor. } if MergeThem then begin {find the left child} if (aKeyIndex = 0) then LChild := bhiPrevPageRef else LChild := PPageNum(@aParentPage^[ffc_BlockHeaderSizeIndex + ((aKeyIndex-1) * SizeOfPageNum)])^; with aIndexData do LChildPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, LChild, aLCRelMethod); { Does this child page have enough keys? } if (LChildPageHdr^.bhiKeyCount > (LChildPageHdr^.bhiMaxKeyCount div 2)) then begin {$IFDEF FF_DEBUG} inc(FFDEBUG_IndexCounter.SwapPrev); {$ENDIF} {save the left child page as result} aRelMethod := aLCRelMethod; Result := LChildPage; ResultPageNum := LChild; {prepare to walk down the btree} Child := LChild; ChildPage := LChildPage; aChildRelMeth := aLCRelMethod; {continue walking down the btree going right all the time until we hit a leaf, locking as we go} while (not ChildPageHdr^.bhiIsLeafPage) do begin Child := PPageNum(@ChildPage^[ffc_BlockHeaderSizeIndex + ((ChildPageHdr^.bhiKeyCount-1) * SizeOfPageNum)])^; if ChildPage <> LChildPage then {!!.01} aChildRelMeth(ChildPage); {!!.01} with aIndexData do ChildPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aChildRelMeth); end; { Mark the leaf child dirty because we shall be updating it. } FFBMDirtyBlock(aIndexData.kidFI, Child, aTI, ChildPage); // if ResultPageNum = Child then begin {Deleted !!.01} // aRelMethod := aChildRelMeth; {Deleted !!.01} // Result := ChildPage; {Deleted !!.01} // end; {Deleted !!.01} { Swap the child's largest key & ref with this key in the parent. } SwapKeys(aParentPage, aKeyIndex, ChildPage, pred(ChildPageHdr^.bhiKeyCount), aKey); {Begin !!.01} { Did we modify the left child? } if Child = ResultPageNum then { Yes. Make sure we return the child page's modified block to the calling method. } Result := ChildPage else { No. Release the child page that we modified. } aChildRelMeth(ChildPage); {End !!.01} MergeThem := false; end; end; {if we've failed to find the predecessor/successor, merge the two children} if MergeThem then begin { Obtain an Exclusive lock on the children. } {***Delphi32***: the compiler tags LChild as possibly being "used before definition". Not true.} FFBMDirtyBlock(aIndexData.kidFI, LChild, aTI, LChildPage); FFBMDirtyBlock(aIndexData.kidFI, RChild, aTI, RChildPage); { Merge them. } MergeChildren(aIndexData, aTI, aParentPage, aKeyIndex, LChildPage, RChildPage); aRelMethod := aLCRelMethod; Result := LChildPage; aRCRelMethod(RChildPage); end; end; {***Delphi32***: the compiler tags the return value of this function as being "possibly undefined". Not true.} end; {--------} procedure BtreeDeleteRedistributeOrMerge(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aParentPage : PffBlock; aChildIndex : integer; aChildPage : PffBlock); var aParentPageHdr : PffBlockHeaderIndex absolute aParentPage; SiblingPage : PffBlock; SiblingPageHdr : PffBlockHeaderIndex absolute SiblingPage; Sibling : Longint; PageBlock : PPageNumBlock; DoneIt : boolean; IsRightSibling : boolean; aRelList : TffPointerList; aRelMethod : TffReleaseMethod; begin {Assumptions: aParentPage and aChildPage have both been marked dirty. aChildPage has the minimum number of keys. } aRelList := TffPointerList.Create; Sibling := 0; IsRightSibling := false; SiblingPage := nil; try {assume we shall fail all the way} DoneIt := false; {read the child's successor sibling page} if (aChildIndex < aParentPageHdr^.bhiKeyCount) then begin PageBlock := PPageNumBlock(@aParentPage^[ffc_BlockHeaderSizeIndex]); Sibling := PageBlock^[aChildIndex]; IsRightSibling := true; with aIndexData do SiblingPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Sibling, aRelMethod); aRelList.Append(FFAllocReleaseInfo(SiblingPage, TffInt64(aRelMethod))); {check for at least one spare key} if (SiblingPageHdr^.bhiKeyCount > (SiblingPageHdr^.bhiMaxKeyCount div 2)) then begin { Mark the sibling as dirty. } FFBMDirtyBlock(aIndexData.kidFI, Sibling, aTI, SiblingPage); { Redistribute the keys. } if (not SiblingPageHdr^.bhiIsLeafPage) then RotateLeftNode(aParentPage, aChildIndex, aChildPage, SiblingPage) else RotateLeftLeaf(aParentPage, aChildIndex, aChildPage, SiblingPage); DoneIt := true; end; end; { Read the child's predecessor sibling page. } if (not DoneIt) and (aChildIndex > 0) then begin if (aChildIndex = 1) then Sibling := aParentPageHdr^.bhiPrevPageRef else begin PageBlock := PPageNumBlock(@aParentPage^[ffc_BlockHeaderSizeIndex]); Sibling := PageBlock^[aChildIndex-2]; end; IsRightSibling := false; with aIndexData do SiblingPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Sibling, aRelMethod); aRelList.Append(FFAllocReleaseInfo(SiblingPage, TffInt64(aRelMethod))); { Check for at least one spare key. } if (SiblingPageHdr^.bhiKeyCount > (SiblingPageHdr^.bhiMaxKeyCount div 2)) then begin { Obtain an Exclusive lock on the sibling. } FFBMDirtyBlock(aIndexData.kidFI, Sibling, aTI, SiblingPage); { Redistribute the keys. } if (not SiblingPageHdr^.bhiIsLeafPage) then RotateRightNode(aParentPage, aChildIndex-1, SiblingPage, aChildPage) else RotateRightLeaf(aParentPage, aChildIndex-1, SiblingPage, aChildPage); DoneIt := true; end; end; {***Delphi32***: The compiler tags both Sibling and IsRightSibling as possibly being "used before definition". A corollary of the definition of a B-Tree insists that every child page has at least one sibling and so in practice both variables will be set by this point.} if (not DoneIt) then begin { Mark the sibling as dirty. } FFBMDirtyBlock(aIndexData.kidFI, Sibling, aTI, SiblingPage); { Merge with our sibling. } if IsRightSibling then MergeChildren(aIndexData, aTI, aParentPage, aChildIndex, aChildPage, SiblingPage) else MergeChildren(aIndexData, aTI, aParentPage, aChildIndex-1, SiblingPage, aChildPage) end; finally for Sibling := 0 to pred(aRelList.Count) do FFDeallocReleaseInfo(aRelList[Sibling]); aRelList.Free; end; end; {--------} function BtreeDeleteAmplePage(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aPage : PffBlock; aKey : PffByteArray; const aRefNr : TffInt64; var aBTreeChanged : Boolean) {!!.05} : Boolean; {-Routine to delete a key from a page; only called for pages that have succ(minimum keys) present, or for the root} var Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; L, R, M : integer; CompResult : integer; Child : Longint; ChildPage : PffBlock; ChildPageHdr : PffBlockHeaderIndex absolute ChildPage; Compare : TffKeyCompareFunc; AllowDups : boolean; KeyFound : boolean; DoneRecursing: boolean; aRelMethod : TffReleaseMethod; aRelList : TffPointerList; begin {$IFDEF DefeatWarnings} M := 0; Result := false; {$ENDIF} Page := aPage; aRelMethod := nil; { We use the following list to track the RAM pages we've accessed and the release method associated with each RAM page. At the end of this routine, we will call the release method for each RAM page. } aRelList := TffPointerList.Create; try { Assumption: Page has not been marked dirty. } { Learn whether dup keys are allowed, get compare function. } with aIndexData do begin AllowDups := (kidIndexHeader^.bihIndexFlags[kidIndex] and ffc_InxFlagAllowDups) <> 0; Compare := kidCompare; end; {simulate recursion (ie unwind it)} DoneRecursing := false; repeat with PageHdr^ do begin {get the addresses of the reference block and key string block} if bhiIsLeafPage then begin PageNumBlock := nil; DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); end else begin PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); end; {binary search to find out if key is present} L := 0; R := pred(bhiKeyCount); KeyFound := false; {note: it is possible for this routine to be called for an empty root} if (R >= 0) then repeat M := (L + R) div 2; CompResult := Compare(aKey^, KeyBlock^[M * bhiKeyLength], aIndexData.kidCompareData); if (CompResult < 0) then R := pred(M) else if (CompResult > 0) then L := succ(M) else {CompResult = 0} if AllowDups then begin CompResult := FFCmpI64(aRefNr, DataRefBlock^[M]); if (CompResult < 0) then R := pred(M) else if (CompResult > 0) then L := succ(M) else {key+refnr have been found} begin KeyFound := true; Break;{out of the repeat..until loop} end end else {key has been found} begin KeyFound := true; Break;{out of the repeat..until loop} end until (L > R); {if the page is a leaf...} if bhiIsLeafPage then begin {if the key was found delete it from the page, return true} if KeyFound then begin { Mark the block as dirty. } FFBMDirtyBlock(aIndexData.kidFI, bhiThisBlock, aTI, Page); {***Delphi32***: the compiler flags M as "possibly used before definition". Not true.} RemoveKeyFromLeafPage(Page, M); Result := true; end {otherwise return false} else Result := false; DoneRecursing := true; end { Otherwise the page is an internal node... } else { If the key was found in the node... } if KeyFound then begin {we need to swap this key with its predecessor/successor (this is guaranteed to be on a leaf) then delete the key in the leaf} { Mark the block as dirty. } FFBMDirtyBlock(aIndexData.kidFI, bhiThisBlock, aTI, Page); { Swap the key with a key on a leaf, or merge children, then recursively delete from returned child. } Page := BtreeDeleteSwapKey(aIndexData, aTI, Page, M, aKey, aRelMethod); aBtreeChanged := True; {!!.05} aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); end {otherwise the key was not found...} else begin {the key, if anywhere, is in the child subtree at L-1} if (L = 0) then Child := bhiPrevPageRef else Child := PageNumBlock^[pred(L)]; {read the child's page} with aIndexData do ChildPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aRelMethod); aRelList.Append(FFAllocReleaseInfo(ChildPage, TffInt64(aRelMethod))); {check whether the child has enough keys, if so recurse} if (ChildPageHdr^.bhiKeyCount > (ChildPageHdr^.bhiMaxKeyCount div 2)) then Page := ChildPage {otherwise try and make it full enough} else {not enough keys in child} begin { Mark this page and the child as dirty. } FFBMDirtyBlock(aIndexData.kidFI, bhiThisBlock, aTI, Page); FFBMDirtyBlock(aIndexData.kidFI, Child, aTI, ChildPage); { Redistribute the keys among siblings, or merge. } BtreeDeleteRedistributeOrMerge(aIndexData, aTI, Page, L, ChildPage); aBTreeChanged := True; {!!.05} {recurse ourselves} {Note: it could be that we now have only the minimum number of keys, but it doesn't matter since we'll immediately recurse into one of our children} end; end; end; until DoneRecursing; {***Delphi32***: the compiler tags the return value of this function as being "possibly undefined". Not true, DoneRecursing is only set true once Result has been set true/false} finally for Child := 0 to pred(aRelList.Count) do FFDeallocReleaseInfo(aRelList[Child]); aRelList.Free; end; end; {--------} function BtreeDelete(const aRoot : Longint; const aKey : PffByteArray; const aTI : PffTransInfo; const aIndexData : TffKeyIndexData; const aRefNr : TffInt64; var aBTreeChanged : Boolean) {!!.05} : Boolean; var RootPage, RootPageClone : PffBlock; RootPageHdr : PffBlockHeaderIndex absolute RootPage; RootPageCloneHdr : PffBlockHeaderIndex absolute RootPageClone; aCloneRelMethod, aRelMethod : TffReleaseMethod; begin { Obtain the root page. } with aIndexData do RootPage := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, aRoot, aRelMethod); try { Delete the key from this page. } Result := BtreeDeleteAmplePage(aIndexData, aTI, RootPage, aKey, aRefNr, aBTreeChanged); {!!.05} { If the root page is empty then replace it with its first child & delete the root page. } if Result then begin { Get the root page as it may have been modified. We may be looking at the read-only block right now and we need the modified block. } RootPageClone := FFBMGetBlock(aIndexData.kidFI, aTI, aRoot, ffc_ReadOnly, aCloneRelMethod); if (RootPageCloneHdr^.bhiKeyCount = 0) then { Assumption: The root page has been exclusively locked somewhere in the delete process. } with aIndexData do begin kidIndexHeader^.bihIndexRoot[kidIndex] := RootPageCloneHdr^.bhiPrevPageRef; FFTblHlpDeleteBlock(kidFI, kidFileHeader, RootPageClone); dec(kidIndexHeader^.bihIndexPageCount[kidIndex]); end; aCloneRelMethod(RootPageClone); end; finally aRelMethod(RootPage); end; end; {====================================================================} {===Key reading helper routines======================================} function BtreeExistsKey(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aRoot : TffWord32; aKey : PffByteArray; aRefNr : TffInt64) : boolean; var Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; L, R, M : integer; CompResult : integer; Child : TffWord32; Compare : TffKeyCompareFunc; CheckDups : boolean; KeyFound : boolean; DoneRecursing: boolean; aRelMethod : TffReleaseMethod; begin {$IFDEF DefeatWarnings} Result := false; {$ENDIF} {get the root page} with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, aRoot, aRelMethod); try {set up the invariants} with aIndexData do begin CheckDups := ((kidIndexHeader^.bihIndexFlags[kidIndex] and ffc_InxFlagAllowDups) <> 0) and (aRefNr.iLow <> 0) and (aRefNr.iHigh <> 0); Compare := kidCompare; end; {simulate recursion (ie unwind it)} DoneRecursing := false; repeat with PageHdr^ do begin {get the addresses of the reference block and key string block} if bhiIsLeafPage then begin PageNumBlock := nil; DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); end else begin PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount*SizeOfPageNum)]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); end; {binary search to find out if key is present} L := 0; R := pred(bhiKeyCount); KeyFound := false; repeat M := (L + R) div 2; CompResult := Compare(aKey^, KeyBlock^[M * bhiKeyLength], aIndexData.kidCompareData); if (CompResult < 0) then R := pred(M) else if (CompResult > 0) then L := succ(M) else {CompResult = 0} if CheckDups then begin CompResult := FFCmpI64(aRefNr, DataRefBlock^[M]); if (CompResult < 0) then R := pred(M) else if (CompResult > 0) then L := succ(M) else {key+refnr have been found} begin KeyFound := true; Break;{out of the repeat..until loop} end end else {key has been found} begin KeyFound := true; Break;{out of the repeat..until loop} end; until (L > R); if KeyFound then begin Result := true; DoneRecursing := true; end else {if the page is a leaf...} if bhiIsLeafPage then begin {the key was not found at all} Result := false; DoneRecursing := true; end {otherwise the page is an internal node...} else begin {the key, if anywhere, is in the child subtree at L-1} if (L = 0) then Child := bhiPrevPageRef else Child := PageNumBlock^[pred(L)]; {read the child's page} aRelMethod(Page); with aIndexData do begin Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aRelMethod); end; {and recurse it} end; end; until DoneRecursing; {***Delphi32***: the compiler tags the return value of this function as being "possibly undefined". Not true, DoneRecursing is only set true once Result has been set true/false} finally aRelMethod(Page); end; end; {--------} function BtreeNextKey(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aKey : PffByteArray; var aRefNr : TffInt64; var aKeyPath : TffKeyPath) : boolean; var aInx : Longint; Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; PageNum : TffWord32; aRelList : TffPointerList; aRelMethod : TffReleaseMethod; begin { Assumption: the btree has at least one key. } aRelList := TffPointerList.Create; try with aKeyPath do begin {patch the path for BOF} if (kpPos = kppBOF) then begin kpPath[0].kpePage := aIndexData.kidIndexHeader^.bihIndexRoot[aIndexData.kidIndex]; kpPath[0].kpeItem := -1; kpCount := 1; end; {get the last page on the key path} with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, kpPath[pred(kpCount)].kpePage, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); {if we're on a crack, just return the key pointed to by the path} if (kpPos = kppOnCrackBefore) and {!!.03 - Start} (kpPath[pred(kpCount)].kpeItem <= pred(PageHdr^.bhiKeyCount)) then {!!.03 - End} with kpPath[pred(kpCount)], PageHdr^ do begin if bhiIsLeafPage then DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]) else DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else if bhiIsLeafPage then KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount*SizeOfRef)]) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); Result := true; Exit; end; {if the current page is a node, we need to travel down the btree, going left all the time, until we hit a leaf} if not PageHdr^.bhiIsLeafPage then begin {read the first child} PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); if (kpPath[pred(kpCount)].kpeItem = -1) then PageNum := PageHdr^.bhiPrevPageRef else PageNum := PageNumBlock^[kpPath[pred(kpCount)].kpeItem]; with aIndexData do begin Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, PageNum, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); end; while not PageHdr^.bhiIsLeafPage do begin with kpPath[kpCount] do begin kpePage := PageHdr^.bhiThisBlock; kpeItem := -1; end; inc(kpCount); with aIndexData do begin Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, PageHdr^.bhiPrevPageRef, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); end; end; with kpPath[kpCount], PageHdr^ do begin kpePage := PageHdr^.bhiThisBlock; kpeItem := 0; inc(kpCount); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); aRefNr := DataRefBlock^[0]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); Move(KeyBlock^[0], aKey^, bhiKeyLength); end; Result := true; end {otherwise the current page is a leaf} {if the current item is not the final key, just return the next} else if (kpPath[pred(kpCount)].kpeItem < pred(PageHdr^.bhiKeyCount)) then begin with kpPath[pred(kpCount)], PageHdr^ do begin inc(kpeItem); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); Result := true; end; end {otherwise the current item is the last key on the page, we need to travel up the btree returning along the path, until we get to a node where the current item is less than the number of keys; if we can't find one, return false--there is no next key} else begin {read the first parent, assume we won't find a next key} dec(kpCount); Result := false; {while there are still items in the key path} while (kpCount > 0) do begin {read the current page} with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, kpPath[pred(kpCount)].kpePage, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); {if the current item is not the final key, just return the next} if (kpPath[pred(kpCount)].kpeItem < pred(PageHdr^.bhiKeyCount)) then begin with kpPath[pred(kpCount)], PageHdr^ do begin inc(kpeItem); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); end; Result := true; Break;{out of dowhile loop} end; {otherwise go back one step} dec(kpCount); end; end; { if } end; { with } finally for aInx := 0 to pred(aRelList.Count) do FFDeallocReleaseInfo(aRelList[aInx]); aRelList.Free; end; end; {--------} function BtreePrevKey(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aKey : PffByteArray; var aRefNr : TffInt64; var aKeyPath : TffKeyPath) : boolean; var aInx : Longint; Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; PageNum : TffWord32; aRelList : TffPointerList; aRelMethod : TffReleaseMethod; begin {Assumption: the btree has at least one key if the path is pointing to EOF the root page can be found at aKeyPath.kpPath[0].kpePage} aRelList := TffPointerList.Create; try with aKeyPath do begin {if the keypath points to EOF, then read the root page and set the item number of the first path element to the count of keys ready for the walk down the btree} if (kpPos = kppEOF) then begin with kpPath[0], aIndexData do begin kpePage := kidIndexHeader^.bihIndexRoot[kidIndex]; Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, kpePage, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); kpeItem := PageHdr^.bhiKeyCount; end; kpCount := 1; end else begin {get the last page on the key path} with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, kpPath[pred(kpCount)].kpePage, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); {if we're on a crack, just return the key pointed to by the path} if (kpPos = kppOnCrackAfter) then with kpPath[pred(kpCount)], PageHdr^ do begin if bhiIsLeafPage then DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]) else DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else if bhiIsLeafPage then KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); Result := true; Exit; end; { with } end; { if } {if the current page is a node, we need to travel down the btree, going right all the time after the first child, until we hit a leaf} if not PageHdr^.bhiIsLeafPage then begin {read the first (ie previous) child} dec(kpPath[pred(kpCount)].kpeItem); PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); if (kpPath[pred(kpCount)].kpeItem < 0) then PageNum := PageHdr^.bhiPrevPageRef else PageNum := PageNumBlock^[kpPath[pred(kpCount)].kpeItem]; with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, PageNum, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); while not PageHdr^.bhiIsLeafPage do begin with kpPath[kpCount], PageHdr^ do begin kpePage := bhiThisBlock; kpeItem := pred(bhiKeyCount); end; inc(kpCount); PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); with aIndexData do begin PageNum := PageNumBlock^[pred(PageHdr^.bhiKeyCount)]; Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, PageNum, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); end; end; with kpPath[kpCount], PageHdr^ do begin kpePage := bhiThisBlock; kpeItem := pred(bhiKeyCount); inc(kpCount); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); end; Result := true; end {otherwise the current page is a leaf} {if the current item is not the first key, just return the previous} else if (kpPath[pred(kpCount)].kpeItem > 0) then begin with kpPath[pred(kpCount)], PageHdr^ do begin dec(kpeItem); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); Result := true; end; end {otherwise the current item is the first key on the page, we need to travel up the btree returning along the path, until we get to a node where the current item is not the first key on the page; if we can't find one, return false--there is no previous key} else begin {read the first parent, assume we won't find a previous key} dec(kpCount); Result := false; {while there are still items in the key path} while (kpCount > 0) do begin {read the current page} with aIndexData do begin PageNum := kpPath[pred(kpCount)].kpePage; Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, PageNum, aRelMethod); aRelList.Append(FFAllocReleaseInfo(Page, TffInt64(aRelMethod))); end; {if the current item is not -1, just return it} if (kpPath[pred(kpCount)].kpeItem >= 0) then begin with kpPath[pred(kpCount)], PageHdr^ do begin DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); end; Result := true; Break;{out of dowhile loop} end; {otherwise go back one step} dec(kpCount); end; end; end; { with } finally for aInx := 0 to pred(aRelList.Count) do FFDeallocReleaseInfo(aRelList[aInx]); aRelList.Free; end; end; {--------} function BtreeFindKey(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aRoot : TffWord32; aKey : PffByteArray; var aRefNr : TffInt64; var aKeyPath : TffKeyPath; aAction : TffSearchKeyAction) : boolean; var Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; OurKey : PffByteArray; KeyLen : integer; L, R, M : integer; KeyCompResult: integer; RefCompResult: integer; Child : TffWord32; Compare : TffKeyCompareFunc; CheckDups : boolean; KeyFound : boolean; DoneRecursing: boolean; HasDups : boolean; aRelMethod : TffReleaseMethod; begin { Get the root page. } with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, aRoot, aRelMethod); try { Set up the invariants. } with aIndexData do begin {HasDups means that there might be duplicate keys here} HasDups := (kidIndexHeader^.bihIndexFlags[kidIndex] and ffc_InxFlagAllowDups) <> 0; {CheckDups means that we're trying to find an exact key/refnr combination} CheckDups := HasDups and ((aRefNr.iLow <> 0) or (aRefNr.iHigh <> 0)); Compare := kidCompare; end; { Prepare the key path. } FFInitKeyPath(aKeyPath); {simulate recursion (ie unwind it)} DoneRecursing := false; repeat with PageHdr^, aKeyPath do begin {get the addresses of the reference block and key string block} if bhiIsLeafPage then begin PageNumBlock := nil; DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); end else begin PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfPageNum)]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); end; {binary search to find out if key is present} L := 0; R := pred(bhiKeyCount); KeyFound := false; repeat M := (L + R) div 2; KeyCompResult := Compare(aKey^, KeyBlock^[M * bhiKeyLength], aIndexData.kidCompareData); if (KeyCompResult < 0) then R := pred(M) else if (KeyCompResult > 0) then L := succ(M) else {KeyCompResult = 0} if CheckDups then begin RefCompResult := FFCmpI64(aRefNr, DataRefBlock^[M]); if (RefCompResult < 0) then R := pred(M) else if (RefCompResult > 0) then L := succ(M) else {key+refnr have been found} begin KeyFound := true; Break;{out of the repeat..until loop} end end else {key has been found} begin KeyFound := true; Break;{out of the repeat..until loop} end; until (L > R); {if the key/ref was found then save the final keypath element} if KeyFound then begin DoneRecursing := true; with kpPath[kpCount] do begin kpePage := bhiThisBlock; kpeItem := M; end; inc(kpCount); aRefNr := DataRefBlock^[M]; Move(KeyBlock^[M * bhiKeyLength], aKey^, bhiKeyLength); kpPos := kppOnKey; end else {if the page is a leaf...} if bhiIsLeafPage then begin {if the index allows dups, the key has been matched and the passed refnr is zero, return the first refnr in the index for the key} if CheckDups and (KeyCompResult = 0) and (aRefNr.iLow = 0) and (aRefNr.iHigh = 0) then begin KeyFound := true; DoneRecursing := true; with kpPath[kpCount] do begin kpePage := bhiThisBlock; kpeItem := L; end; inc(kpCount); aRefNr := DataRefBlock^[L]; kpPos := kppOnCrackBefore; end else begin {the key/ref was not present at all, patch the final keypath node according to the aAction parameter} DoneRecursing := true; with kpPath[kpCount] do begin kpePage := bhiThisBlock; case aAction of skaEqual : FFInitKeyPath(aKeyPath); skaEqualCrack, skaGreater, skaGreaterEqual : begin if (L < bhiKeyCount) then begin kpeItem := L; kpPos := kppOnCrackBefore; end else begin kpeItem := pred(L); kpPos := kppOnCrackAfter; end; end; end;{case} end; inc(kpCount); end; end {otherwise the page is an internal node...} else begin {the key, if anywhere, is in the child subtree at L-1} with kpPath[kpCount] do begin kpePage := bhiThisBlock; kpeItem := pred(L); end; inc(kpCount); if (L = 0) then Child := bhiPrevPageRef else Child := PageNumBlock^[pred(L)]; {read the child's page} aRelMethod(Page); with aIndexData do begin { Crab down to child block and unlock parent block. } Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aRelMethod); end; {and recurse it} end; end; until DoneRecursing; {if the key wasn't found...} if (not KeyFound) then begin { If we don't mind the missing key and can accept being positioned on a crack before the next key, our search is over. } if (aAction = skaEqualCrack) then KeyFound := true {if we can return the next greater key, do so; always return true (the caller will be patching the keypath)} else if (aAction <> skaEqual) then begin if BtreeNextKey(aIndexData, aTI, aKey, aRefNr, aKeyPath) then aKeyPath.kpPos := kppOnKey else {we hit the end of the index; this is OK, just set the keypath to EOF} FFSetKeyPathToEOF(aKeyPath); KeyFound := true; end; end {otherwise the key was found...} else {KeyFound is true} begin {if we actually wanted the next greater key, continue doing next key operations until the key returned compares unequal to the one we have, or we hit EOF; always return true} if (aAction = skaGreater) then begin KeyLen := aIndexData.kidCompareData^.cdKeyLen; FFGetMem(OurKey, KeyLen); try Move(aKey^, OurKey^, KeyLen); repeat KeyFound := BtreeNextKey(aIndexData, aTI, aKey, aRefNr, aKeyPath); if KeyFound then begin aKeyPath.kpPos := kppOnKey; KeyFound := Compare(aKey^, OurKey^, aIndexData.kidCompareData) = 0 end else FFSetKeyPathToEOF(aKeyPath); until (not KeyFound); finally FFFreeMem(OurKey, KeyLen); end; end {otherwise we wanted an equal key} else {aAction <> skaGreater} begin {if we were making an exact full key match on an index with unique keys, we're done now; otherwise we have to position the keypath at the first of possibly many equal partial keys, or equal duplicate keys. Note that if the index has dup keys, but we've matched exactly on the refnr as well, then we've found the exact key} if (HasDups and not CheckDups) or (aIndexData.kidCompareData^.cdFldCnt <> 0) or (aIndexData.kidCompareData^.cdPartLen <> 0) then begin KeyLen := aIndexData.kidCompareData^.cdKeyLen; FFGetMem(OurKey, KeyLen); try Move(aKey^, OurKey^, KeyLen); repeat KeyFound := BtreePrevKey(aIndexData, aTI, aKey, aRefNr, aKeyPath); if KeyFound then KeyFound := Compare(aKey^, OurKey^, aIndexData.kidCompareData) = 0 else FFSetKeyPathToBOF(aKeyPath); until (not KeyFound); BtreeNextKey(aIndexData, aTI, aKey, aRefNr, aKeyPath); aKeyPath.kpPos := kppOnKey; finally FFFreeMem(OurKey, KeyLen); end; end; end; {make sure that KeyFound is still true, we may have altered it} KeyFound := true; end; Result := KeyFound; finally aRelMethod(Page); end; end; {--------} procedure BtreeFindApprox(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aRoot : TffWord32; aKey : PffByteArray; var aRefNr : TffInt64; var aKeyPath : TffKeyPath; aPos : integer); var Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; PageNumBlock : PPageNumBlock; DataRefBlock : PRefBlock; KeyBlock : PffByteArray; Child : TffWord32; ChildPos : integer; aRelMethod : TffReleaseMethod; begin {get the root page} with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, aRoot, aRelMethod); try {if the root is a leaf, just do a simple calculation to find the key at the approx position} if PageHdr^.bhiIsLeafPage then with aKeyPath, PageHdr^ do begin kpCount := 1; with kpPath[0] do begin kpePage := bhiThisBlock; kpeItem := (aPos * bhiKeyCount) div 101; DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); aRefNr := DataRefBlock^[kpeItem]; if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * SizeOfRef)]); Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); end; end {otherwise the root is a node, ie has children} else with aKeyPath do begin {there will be two levels in the keypath} kpCount := 2; {set up the first entry in the keypath, calc values for the child} with PageHdr^, kpPath[0] do begin kpePage := bhiThisBlock; kpeItem := ((aPos * succ(bhiKeyCount)) div 101) - 1; PageNumBlock := PPageNumBlock(@Page^[ffc_BlockHeaderSizeIndex]); if (kpeItem = -1) then Child := bhiPrevPageRef else Child := PageNumBlock^[kpeItem]; ChildPos := ((aPos * 100) div (101 div succ(bhiKeyCount))) - (succ(kpeItem) * 100); end; {get the child page} aRelMethod(Page); with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, Child, aRelMethod); {set up the second entry in the keypath} with PageHdr^, kpPath[1] do begin kpePage := bhiThisBlock; kpeItem := ((ChildPos * bhiKeyCount) div 101); if bhiIsLeafPage then begin DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount*SizeOfRef)]); end else begin DataRefBlock := PRefBlock(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount*SizeOfPageNum)]); if bhiKeysAreRefs then KeyBlock := PffByteArray(DataRefBlock) else KeyBlock := PffByteArray(@Page^[ffc_BlockHeaderSizeIndex + (bhiMaxKeyCount * (SizeOfPageNum + SizeOfRef))]); end; aRefNr := DataRefBlock^[kpeItem]; Move(KeyBlock^[kpeItem * bhiKeyLength], aKey^, bhiKeyLength); end; end; finally aRelMethod(Page); end; end; {--------} procedure BtreeCalcApprox(const aIndexData : TffKeyIndexData; aTI : PffTransInfo; aRoot : Longint; const aKeyPath : TffKeyPath; var aPos : integer); var Page : PffBlock; PageHdr : PffBlockHeaderIndex absolute Page; RootKeyCount : integer; aRelMethod : TffReleaseMethod; begin {get the root page} with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, aRoot, aRelMethod); try {if the root is a leaf, just do a simple calculation to find approx position of the key} if PageHdr^.bhiIsLeafPage then with aKeyPath.kpPath[0], PageHdr^ do aPos := (kpeItem * 100) div bhiKeyCount {otherwise the root is a node, ie has children} else with aKeyPath do begin {there will be two levels to check in the keypath} RootKeyCount := PageHdr^.bhiKeyCount; {get the relevant child page} aRelMethod(Page); with aIndexData do Page := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeBtreePage, kpPath[1].kpePage, aRelMethod); {calculate the position} if PageHdr^.bhiIsLeafPage then aPos := ((100 + (kpPath[1].kpeItem * 100) div PageHdr^.bhiKeyCount) * succ(kpPath[0].kpeItem)) div succ(RootKeyCount) else aPos := ((100 + (succ(kpPath[1].kpeItem) * 100) div succ(PageHdr^.bhiKeyCount)) * succ(kpPath[0].kpeItem)) div succ(RootKeyCount); end; finally aRelMethod(Page); end; end; {====================================================================} {===Key access related routines======================================} procedure FFTblDeleteAllKeys(aTI : PffTransInfo; var aIndex : TffKeyIndexData); var InxBlock : PffBlock; InxBlockHdr: PffBlockHeaderIndex absolute InxBlock; Root : TffWord32; aInxRelMeth, aRelMethod : TffReleaseMethod; begin {Note: this routine can only be run in a 'subset' transaction. Essentially all the index pages are going to be changed: if they were all in a normal transaction, we could quite easily run out of memory trying to hold all the dirty pages in memory. A corollary is that FFTblDeleteAllKeys cannot be rolled back. Another is that if this crashes, the index file is pretty well hosed and should be rebuilt.} with aIndex do begin { Obtain a Share lock on the file header. We need the file header but we don't need to modify it. } kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try { Get an Exclusive lock on the index header. } InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {get the root page} Root := kidIndexHeader^.bihIndexRoot[kidIndex]; {patch the index header} with kidIndexHeader^ do begin bihIndexRoot[kidIndex] := ffc_W32NoValue; bihIndexPageCount[kidIndex] := 0; end; {special case: if the root page does not exist, just return (ie there are no keys)} if (Root = ffc_W32NoValue) then Exit; { Otherwise go delete all the index pages. } BtreeDeleteIndexPage(aIndex, aTI, Root); { Allow the caller to do the final commit. } finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } end; {--------} function FFTblDeleteKey(const aTI : PffTransInfo; const aKey : PffByteArray; const aRefNr : TffInt64; var aIndex : TffKeyIndexData; var aBTreeChanged : Boolean) : Boolean; {!!.05} var InxBlock : PffBlock; InxBlockHdr: PffBlockHeaderIndex absolute InxBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin with aIndex do begin { Mark the file header as dirty. } kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_MarkDirty, aRelMethod)); try { Obtain an Exclusive lock on the index header. } InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist, return false} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then Result := false {otherwise go delete from the b-tree} else Result := BtreeDelete(kidIndexHeader^.bihIndexRoot[kidIndex], aKey, aTI, aIndex, aRefNr, aBTreeChanged); {!!.05} {decrement the number of keys} if Result then dec(kidIndexHeader^.bihIndexKeyCount[kidIndex]); finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; end; {--------} function FFTblFindKey(var aIndex : TffKeyIndexData; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath; aAction : TffSearchKeyAction) : boolean; var InxBlock : PffBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin with aIndex do begin {get the file header, block 0} kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try {get the index header} InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then begin if (aAction = skaEqual) then Result := false else begin Result:= true; FFSetKeyPathToEOF(aKeyPath); end; end {otherwise go read the b-tree} else Result := BtreeFindKey(aIndex, aTI, kidIndexHeader^.bihIndexRoot[kidIndex], aKey, aRefNr, aKeyPath, aAction); aKeyPath.kpLSN := kidFI^.fiBufMgr.GetRAMPageLSN2 {!!.06} (kidFI, kidFileHeader^.bhfIndexHeader); {!!.06} finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } {if the key was not found, ensure the path is invalidated} if (not Result) and (aAction = skaEqual) then FFInitKeyPath(aKeyPath); end; {--------} function FFTblGetApproxPos(var aIndex : TffKeyIndexData; var aPos : integer; aTI : PffTransInfo; const aKeyPath : TffKeyPath) : boolean; var InxBlock : PffBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin with aIndex do begin {get the file header, block 0} kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try {get the index header} InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist, return false} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then Result := false {otherwise go read the b-tree} else begin Result := true; BtreeCalcApprox(aIndex, aTI, kidIndexHeader^.bihIndexRoot[kidIndex], aKeyPath, aPos); end; finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } end; {--------} function FFTblInsertKey(var aIndex : TffKeyIndexData; const aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray) : boolean; var InxBlock, {!!.11} InxNewBlock : PffBlock; {!!.11} InxBlockHdr: PffBlockHeaderIndex; {!!.11} aInxRelMeth, aInxRelMethNewBlock, {!!.11} aRelMethod : TffReleaseMethod; begin with aIndex do begin { Obtain an Exclusive lock on the file header. } kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_MarkDirty, aRelMethod)); try { Dirty the index header. } InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_MarkDirty, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); { Special case: if the root page does not yet exist, create a new one and add the key to it. } if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then begin {Begin !!.11} // aInxRelMeth(InxBlock); InxNewBlock := GetNewInxBtreeBlock(kidFI, aTI, kidIndexHeader, kidIndex, True, aInxRelMethNewBlock); try InxBlockHdr := PffBlockHeaderIndex(InxNewBlock); kidIndexHeader^.bihIndexRoot[kidIndex] := InxBlockHdr^.bhiThisBlock; InsertKeyInLeafPage(InxNewBlock, 0, aKey, aRefNr); Result := true; finally aInxRelMethNewBlock(InxNewBlock); end; {End !!.11} end {otherwise insert the key in the relevant leaf page} else Result := BtreeInsert(aIndex, aTI, kidIndexHeader^.bihIndexRoot[kidIndex], aKey, aRefNr); {increment the number of keys if key was added} if Result then inc(kidIndexHeader^.bihIndexKeyCount[kidIndex]); finally aInxRelMeth(InxBlock); end; finally aRelMethod(Pffblock(kidFileHeader)); end; end; { with } end; {--------} function FFTblKeyExists(var aIndex : TffKeyIndexData; const aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray ) : boolean; var InxBlock : PffBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin { If the lock duration is ffldShort then this method will free the locks after it has finished searching for the key. } with aIndex do begin {get the file header, block 0} kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try {get the index header} InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist, return false} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then Result := false {otherwise go read the b-tree} else Result := BtreeExistsKey(aIndex, aTI, kidIndexHeader^.bihIndexRoot[kidIndex], aKey, aRefNr); finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } end; {--------} function FFTblNextKey(var aIndex : TffKeyIndexData; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath) : boolean; var // IndexMap : TffbmRAMPage; {Deleted !!.06} IndexMapLSN : TffWord32; {!!.06} InxBlock : PffBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin {if the keypath is valid and it's at EOF, there is no next key} if (aKeyPath.kpPos = kppEOF) then begin Result := false; Exit; end; {otherwise do some work} with aIndex do begin {get the file header, block 0} kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try {get the index header} InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist, return false} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then Result := false {otherwise go read the b-tree} else begin IndexMapLSN := kidFI^.fiBufMgr.GetRAMPageLSN2 {!!.06} (kidFI, kidFileHeader^.bhfIndexHeader); if (aKeyPath.kpPos = kppUnknown) then FFSetKeyPathToBOF(aKeyPath) {Begin !!.06} else begin { Has the index map changed since our last visit? } if (((aKeyPath.kpLSN > 0) and (IndexMapLSN > aKeyPath.kpLSN)) or {!!.06} (aKeyPath.kpPos = kppUnknown)) then begin {!!.05} { Yes. Reposition. } Result := BtreeFindKey(aIndex, aTI, KidIndexHeader^.bihIndexRoot[kidIndex], aKey, aRefNr, aKeyPath, skaEqualCrack); if not Result then Exit; end; end; Result := BtreeNextKey(aIndex, aTI, aKey, aRefNr, aKeyPath); aKeyPath.kpLSN := IndexMapLSN; {!!.06} end; finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } {if a key was found, ensure the path points to a key} if Result then aKeyPath.kpPos := kppOnKey {if no next key found, ensure the path points to EOF} else FFSetKeyPathToEOF(aKeyPath); end; {--------} function FFTblPrevKey(var aIndex : TffKeyIndexData; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath) : boolean; var // IndexMap : TffbmRAMPage; {Deleted !!.06} IndexMapLSN : TffWord32; {!!.06} InxBlock : PffBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin {if the keypath is valid and it's at BOF, there is no prev key} if (aKeyPath.kpPos = kppBOF) then begin Result := false; Exit; end; {otherwise do some work} with aIndex do begin {get the file header, block 0} kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try {get the index header} InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist, return false} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then Result := false {otherwise go read the b-tree} else begin IndexMapLSN := kidFI^.fiBufMgr.GetRAMPageLSN2 {!!.06} (kidFI, kidFileHeader^.bhfIndexHeader); {!!.06} if (aKeyPath.kpPos = kppUnknown) then FFSetKeyPathToEOF(aKeyPath); { Has the index map changed since our last visit? } if (((aKeyPath.kpLSN > 0) and (IndexMapLSN > aKeyPath.kpLSN)) or {!!.06} (aKeyPath.kpPos = kppUnknown)) then begin {!!.05} { Yes. Reposition. } Result := BtreeFindKey(aIndex, aTI, KidIndexHeader^.bihIndexRoot[kidIndex], aKey, aRefNr, aKeyPath, skaEqualCrack); if not Result then Exit; end; Result := BtreePrevKey(aIndex, aTI, aKey, aRefNr, aKeyPath); aKeyPath.kpLSN := IndexMapLSN; {!!.06} end; finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } {if a key was found, ensure the path points to a key} if Result then aKeyPath.kpPos := kppOnKey {if no previous key found, ensure the path points to BOF} else FFSetKeyPathToBOF(aKeyPath); end; {--------} function FFTblSetApproxPos(var aIndex : TffKeyIndexData; aPos : integer; var aRefNr : TffInt64; aTI : PffTransInfo; aKey : PffByteArray; var aKeyPath : TffKeyPath) : boolean; var InxBlock : PffBlock; aInxRelMeth, aRelMethod : TffReleaseMethod; begin {validate the position to be 0..100} if (aPos < 0) or (aPos > 100) then with aIndex do FFRaiseException(EffServerException, ffStrResServer, fferrBadApproxPos, [kidFI^.fiName^, kidIndex, aPos]); with aIndex do begin {get the file header, block 0} kidFileHeader := PffBlockHeaderFile(FFBMGetBlock(kidFI, aTI, 0, ffc_ReadOnly, aRelMethod)); try {get the index header} InxBlock := ReadVfyInxBlock(kidFI, aTI, kidFileHeader, ffc_ReadOnly, ffc_InxBlockTypeHeader, kidFileHeader^.bhfIndexHeader, aInxRelMeth); try kidIndexHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); {special case: if the root page does not exist, return false and an invalid keypath} if (kidIndexHeader^.bihIndexRoot[kidIndex] = ffc_W32NoValue) then begin Result := false; FFInitKeyPath(aKeyPath); end {otherwise go read the b-tree} else begin Result := true; BtreeFindApprox(aIndex, aTI, kidIndexHeader^.bihIndexRoot[kidIndex], aKey, aRefNr, aKeyPath, aPos); end; aKeyPath.kpLSN := kidFI^.fiBufMgr.GetRAMPageLSN2 {!!.06} (kidFI, kidFileHeader^.bhfIndexHeader); {!!.06} finally aInxRelMeth(InxBlock); end; finally aRelMethod(PffBlock(kidFileHeader)); end; end; { with } end; {====================================================================} {===Index related routines===========================================} procedure FFTblAddIndex(aFI : PffFileInfo; aTI : PffTransInfo; aIndex : integer; aMaxKeyLen : integer; aAllowDups : boolean; aKeysAreRefs : boolean); var FileHeader : PffBlockHeaderFile; InxBlock : PffBlock; InxHeader : PffIndexHeader; aInxRelMeth, aRelMethod : TffReleaseMethod; begin { First get an Exclusive lock on the file header, block 0. } FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aRelMethod)); try { Second get an Exclusive lock on the index header. } InxBlock := ReadVfyInxBlock(aFI, aTI, FileHeader, ffc_MarkDirty, ffc_InxBlockTypeHeader, FileHeader^.bhfIndexHeader, aInxRelMeth); InxHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); { Set up the index data. } with InxHeader^, FileHeader^ do begin {note that there is only *one* index that uses references as keys: index 0} if aKeysAreRefs then begin bihIndexFlags[aIndex] := ffc_InxFlagKeysAreRefs; {ie no dups!} bihIndexKeyLen[aIndex] := SizeOfRef; bhfHasSeqIndex := 1; end else begin bihIndexKeyLen[aIndex] := aMaxKeyLen; if aAllowDups then bihIndexFlags[aIndex] := ffc_InxFlagAllowDups else bihIndexFlags[aIndex] := 0; end; bihIndexRoot[aIndex] := ffc_W32NoValue; end; aInxRelMeth(InxBlock); finally aRelMethod(PffBlock(FileHeader)); end; end; {--------} procedure FFTblDeleteIndex(aFI : PffFileInfo; aTI : PffTransInfo; aIndex : integer); var FileHeader : PffBlockHeaderFile; InxBlock : PffBlock; InxHeader : PffIndexHeader; Elements : integer; aInxRelMeth, aRelMethod : TffReleaseMethod; begin { First get an Exclusive lock on the file header, block 0.} FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aRelMethod)); try { Second get an Exclusive lock on the index header. } InxBlock := ReadVfyInxBlock(aFI, aTI, FileHeader, ffc_MarkDirty, ffc_InxBlockTypeHeader, FileHeader^.bhfIndexHeader, aInxRelMeth); InxHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); { Remove the index data. } with InxHeader^ do begin if (aIndex < pred(ffcl_MaxIndexes)) then begin Elements := pred(ffcl_MaxIndexes - aIndex); Move(bihIndexKeyLen[succ(aIndex)], bihIndexKeyLen[aIndex], Elements * sizeof(word)); Move(bihIndexFlags[succ(aIndex)], bihIndexFlags[aIndex], Elements * sizeof(byte)); Move(bihIndexRoot[succ(aIndex)], bihIndexRoot[aIndex], Elements * sizeof(Longint)); Move(bihIndexPageCount[succ(aIndex)], bihIndexPageCount[aIndex], Elements * sizeof(Longint)); end; bihIndexKeyLen[pred(ffcl_MaxIndexes)] := 0; bihIndexFlags[pred(ffcl_MaxIndexes)] := 0; bihIndexRoot[pred(ffcl_MaxIndexes)] := ffc_W32NoValue; bihIndexPageCount[pred(ffcl_MaxIndexes)] := 0; end; aInxRelMeth(InxBlock); finally aRelMethod(PffBlock(FileHeader)); end; end; {--------} procedure FFTblPrepareIndexes(aFI : PffFileInfo; aTI : PffTransInfo); var FileHeader : PffBlockHeaderFile; InxBlock : PffBlock; InxBlockHdr : PffBlockHeaderIndex absolute InxBlock; InxHeader : PffIndexHeader; aInxRelMeth, aRelMethod : TffReleaseMethod; begin { First get the file header, block 0. } FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aRelMethod)); try {create the index header block} InxBlock := GetNewInxHeaderBlock(aFI, aTI, aInxRelMeth); with FileHeader^ do begin bhfIndexHeader := InxBlockHdr^.bhiThisBlock; InxHeader := PffIndexHeader(@InxBlock^[ffc_BlockHeaderSizeIndex]); with InxHeader^ do begin {set up the internal fields} FillChar(bihIndexKeyLen, sizeof(bihIndexKeyLen), 0); FillChar(bihIndexFlags, sizeof(bihIndexFlags), 0); FillChar(bihIndexRoot, sizeof(bihIndexRoot), ffc_W32NoValue); FillChar(bihIndexPageCount, sizeof(bihIndexPageCount), 0); end; end; aInxRelMeth(InxBlock); finally aRelMethod(PffBlock(FileHeader)); end; end; {====================================================================} {===Keypath routines=================================================} procedure FFInitKeyPath(var aKeyPath : TffKeyPath); begin FillChar(aKeyPath, sizeof(aKeyPath), 0); end; {--------} procedure FFSetKeyPathToBOF(var aKeyPath : TffKeyPath); begin FillChar(aKeyPath, sizeof(aKeyPath), 0); aKeyPath.kpPos := kppBOF; end; {--------} procedure FFSetKeyPathToEOF(var aKeyPath : TffKeyPath); begin FillChar(aKeyPath, sizeof(aKeyPath), 0); aKeyPath.kpPos := kppEOF; end; {====================================================================} end.