-- BTree.mesa Last edit: KK November 4, 1980 1:43 PM -- Edited by Andrew Birrell 22-May-81 10:19:37 DIRECTORY BTreeDefs: FROM "BTreeDefs" USING [ BTreeHandle, Call, Desc, KeyNotFound, TestKeys], BTree2Defs: FROM "BTree2Defs" USING [ Delete, DeletePage, Entry, EntryR, GetAnother, Index, Initialize, Insert, Items, Lookup, Merger, Replace, VArray], BTreeSupportDefs: FROM "BTreeSupportDefs" USING [ AllocateWords, FileHandle, FreeWords, PageHandle, PageSize, ReadPage, ReleasePage, UsePage, WritePage]; BTree: PROGRAM IMPORTS BTree2Defs, BTreeSupportDefs EXPORTS BTreeDefs = BEGIN TwoKeysNonLeafSplit: SIGNAL RETURNS [Page] = CODE; EntryTooSmall: SIGNAL = CODE; Malformed: SIGNAL = CODE; Alarm: PROCEDURE = BEGIN AlarmCalled END; AlarmCalled: SIGNAL = CODE; Version: NN ← 1; -- for bug checking. Juniper: BOOLEAN = FALSE; CallTree: TYPE = PROCEDURE [p: Page, i: BTree2Defs.Index] RETURNS [more: BOOLEAN]; NN: TYPE = CARDINAL; Page: TYPE = POINTER TO BTreePageRecord; BTreePageRecord: TYPE = RECORD [ b: Tree, pa: NN, ca: BTree2Defs.VArray, c: BTreeDefs.Desc, level: NN, dirty, deleted, rekey: BOOLEAN]; -- pa is the number of this page in the file (starts at 1). Tree: TYPE = POINTER TO BTreeRecord; BTreeRecord: TYPE = RECORD [ version, rootPA, freePA, lastPA, pageS, depth, entries: NN, file: BTreeSupportDefs.FileHandle, self: BTreePageRecord, IsFirstGreaterOrEqual, AreTheyEqual: BTreeDefs.TestKeys, dirtyTree, sawUserUnwind: BOOLEAN]; OldBTreePageRecord: TYPE = RECORD [ b: Tree, pa: NN, ca: BTree2Defs.VArray, c: BTreeDefs.Desc, level: NN, nul1: Page, nul2: NN, dirty, deleted, rekey: BOOLEAN]; OldBTreeRecord: TYPE = RECORD [ version, nlu1, nlu2, nlu3, rootPA, freePA, lastPA, pageS, depth, entries: NN, file: BTreeSupportDefs.FileHandle, oldSelf: OldBTreePageRecord, IsFirstGreaterOrEqual, AreTheyEqual: BTreeDefs.TestKeys, nlu4, dirtyTree: BOOLEAN, nlu5: CARDINAL]; registeryOfBTrees: pntrToBTreeReg ← NIL; pntrToBTreeReg: TYPE = POINTER TO BTreeReg; BTreeReg: TYPE = RECORD [next: pntrToBTreeReg, tree: Tree, valid: BOOLEAN]; nullPA: NN = 177777B; CheckAmount: TYPE = {little, moderate, lots, dontAsk}; checking: CheckAmount ← little; -- there should be a check about pagesize vs. max. item size; PageSize: CARDINAL = BTreeSupportDefs.PageSize[]; InvalidTree: PUBLIC ERROR = CODE; CheckBTreeRegistered: PROCEDURE [b: Tree] RETURNS [inValidTree: BOOLEAN] = BEGIN FOR pntr: pntrToBTreeReg ← registeryOfBTrees, pntr.next DO IF pntr = NIL THEN Alarm[]; IF pntr.tree = b THEN RETURN[NOT pntr.valid]; ENDLOOP; END; RegisterBTree: PROCEDURE [b: Tree] = BEGIN oldPntr: POINTER TO pntrToBTreeReg ← @registeryOfBTrees; pntr: pntrToBTreeReg ← registeryOfBTrees; DO IF pntr = NIL THEN BEGIN pntr ← BTreeSupportDefs.AllocateWords[SIZE[BTreeReg]]; pntr↑ ← [tree: b, next: NIL, valid: TRUE]; oldPntr↑ ← pntr; RETURN; END; oldPntr ← @pntr.next; pntr ← pntr.next; ENDLOOP; END; RemoveRegistryOfBTree: PROCEDURE [b: Tree] = BEGIN oldPntr: POINTER TO pntrToBTreeReg ← @registeryOfBTrees; pntr: pntrToBTreeReg ← registeryOfBTrees; DO IF pntr = NIL THEN Alarm[]; IF pntr.tree = b THEN BEGIN oldPntr↑ ← pntr.next; BTreeSupportDefs.FreeWords[pntr]; RETURN; END; oldPntr ← @pntr.next; pntr ← pntr.next; ENDLOOP; END; MarkBTreeInvalidInRegistry: PROCEDURE [b: Tree] = BEGIN pntr: pntrToBTreeReg ← registeryOfBTrees; DO IF pntr = NIL THEN Alarm[]; IF pntr.tree = b THEN BEGIN pntr.valid ← FALSE; RETURN; END; pntr ← pntr.next; ENDLOOP; END; -- Conventions: Unwinds out of CreateAndInitializeBTree and ReleaseBTree have freed Tree and removed the registry. Other Unwinds expect someone else to shut down the tree (ReleaseBTree is not the thing to be called to do that; call ShutDownBTree instead). CreateAndInitializeBTree: PUBLIC PROCEDURE [ fileH: BTreeSupportDefs.FileHandle, initializeFile, useDefaultOrderingRoutines: BOOLEAN, isFirstGreaterOrEqual, areTheyEqual: BTreeDefs.TestKeys] RETURNS [bh: BTreeDefs.BTreeHandle] = BEGIN b: Tree ← NIL; BEGIN ENABLE UNWIND => IF b # NIL THEN BEGIN RemoveRegistryOfBTree[b]; BTreeSupportDefs.FreeWords[b]; END; b ← BTreeSupportDefs.AllocateWords[SIZE[BTreeRecord]]; RegisterBTree[b]; bh ← LOOPHOLE[b]; IF initializeFile THEN BEGIN b↑ ← [ version: Version, rootPA: nullPA, freePA: 0, lastPA: 1, pageS: PageSize, depth: 177777B, entries: 0, file: fileH, self: BTreePageRecord[ b: b, pa: 1, ca:, c:, level: 1, dirty: TRUE, deleted: FALSE, rekey: FALSE], IsFirstGreaterOrEqual:, AreTheyEqual:, dirtyTree: TRUE, sawUserUnwind: FALSE]; PushBTreeToDisk[bh]; -- just to keep WritePage happy about order of writes. BuildARoot[b, NIL]; END ELSE BEGIN oldB: POINTER TO OldBTreeRecord ← LOOPHOLE[BTreeSupportDefs.ReadPage[ fileH, 1 - 1]]; IF oldB.version # Version THEN Malformed; IF oldB.pageS # PageSize THEN Malformed; b↑ ← [ version: oldB.version, rootPA: oldB.rootPA, freePA: oldB.freePA, lastPA: oldB.lastPA, pageS: PageSize, depth: oldB.depth, entries: oldB.entries, file: fileH, self: [ b: b, pa: oldB.oldSelf.pa, ca: oldB.oldSelf.ca, c: oldB.oldSelf.c, level: oldB.oldSelf.level, dirty: FALSE, deleted: FALSE, rekey: FALSE], IsFirstGreaterOrEqual:, AreTheyEqual:, dirtyTree: FALSE, sawUserUnwind: FALSE]; BTreeSupportDefs.ReleasePage[LOOPHOLE[oldB, BTreeSupportDefs.PageHandle]]; END; b.IsFirstGreaterOrEqual ← (IF useDefaultOrderingRoutines THEN MyIsFirstGreaterOrEqual ELSE isFirstGreaterOrEqual); b.AreTheyEqual ← (IF useDefaultOrderingRoutines THEN MyAreTheyEqual ELSE areTheyEqual); IF b.version # Version THEN Alarm[]; END; END; ReleaseBTree: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] RETURNS [fileH: BTreeSupportDefs.FileHandle] = BEGIN b: Tree = LOOPHOLE[bh]; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; PushBTreeToDisk[ bh ! UNWIND => BEGIN RemoveRegistryOfBTree[b]; BTreeSupportDefs.FreeWords[b]; END]; fileH ← b.file; RemoveRegistryOfBTree[b]; BTreeSupportDefs.FreeWords[b]; RETURN[fileH]; END; ShutDownBTree: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] RETURNS [fileH: BTreeSupportDefs.FileHandle] = BEGIN b: Tree = LOOPHOLE[bh]; IF NOT CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; RemoveRegistryOfBTree[b]; BTreeSupportDefs.FreeWords[b]; RETURN[b.file]; END; PushBTreeToDisk: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] = BEGIN b: Tree = LOOPHOLE[bh]; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; IF b.dirtyTree THEN BEGIN oldB: POINTER TO OldBTreeRecord ← LOOPHOLE[BTreeSupportDefs.UsePage[ b.file, 0]]; oldB↑ ← [ version: b.version, nlu1:, nlu2:, nlu3:, rootPA: b.rootPA, freePA: b.freePA, lastPA: b.lastPA, pageS: b.pageS, depth: b.depth, entries: b.entries, file:, oldSelf: [ b:, pa: b.self.pa, ca: b.self.ca, c: b.self.c, level: b.self.level, nul1:, nul2:, dirty:, deleted:, rekey:], IsFirstGreaterOrEqual:, AreTheyEqual:, nlu4:, dirtyTree:, nlu5:]; BTreeSupportDefs.WritePage[ b.file, 0, LOOPHOLE[oldB, BTreeSupportDefs.PageHandle] ! UNWIND => BEGIN BTreeSupportDefs.ReleasePage[ LOOPHOLE[oldB, BTreeSupportDefs.PageHandle]]; MarkBTreeInvalidInRegistry[b]; END]; b.dirtyTree ← FALSE; END; IF b.version # Version THEN Alarm[]; END; PruneBTree: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] RETURNS [storageFreed: BOOLEAN] = BEGIN b: Tree = LOOPHOLE[bh]; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; RETURN[FALSE]; END; AskNumberOfEntriesInTree: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] RETURNS [nEntries: CARDINAL] = BEGIN b: Tree = LOOPHOLE[bh]; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; IF ((b.entries = 0) OR (checking >= lots)) THEN CheckTree[bh]; RETURN[b.entries]; END; AskDepthOfTree: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] RETURNS [nLevels: CARDINAL] = BEGIN b: Tree = LOOPHOLE[bh]; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; RETURN[b.depth]; END; Insert: PUBLIC PROCEDURE [ bh: BTreeDefs.BTreeHandle, key: BTreeDefs.Desc, value: BTreeDefs.Desc] = BEGIN b: Tree = LOOPHOLE[bh]; eR: BTree2Defs.EntryR ← [key, value]; InsertC: CallTree = BEGIN IF b.AreTheyEqual[key, LookupP[p, i].k] THEN ReplaceP[p, i, @eR] ELSE BEGIN InsertP[p, i, @eR]; b.entries ← b.entries + 1; END; b.dirtyTree ← TRUE; RETURN[more: FALSE]; END; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; IF LENGTH[key] = 0 THEN ERROR; StartEnumerate[bh, key, InsertC]; IF b.version # Version THEN Alarm[]; END; Lookup: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle, key, value: BTreeDefs.Desc] RETURNS [lengthValue: CARDINAL] = BEGIN bcheck: Tree = LOOPHOLE[bh]; LookupC: CallTree = BEGIN b: Tree = LOOPHOLE[bh]; eR: BTree2Defs.EntryR ← LookupP[p, i]; lengthValue ← LENGTH[eR.v]; IF b.AreTheyEqual[key, eR.k] THEN Copy[from: eR.v, to: value] ELSE lengthValue ← BTreeDefs.KeyNotFound; RETURN[more: FALSE]; END; IF CheckBTreeRegistered[bcheck].inValidTree THEN ERROR InvalidTree; IF bcheck.version # Version THEN Alarm[]; StartEnumerate[bh, key, LookupC]; IF bcheck.version # Version THEN Alarm[]; END; Next: PROCEDURE [bh: BTreeDefs.BTreeHandle, e: BTree2Defs.Entry] = BEGIN seen: CARDINAL ← 0; bcheck: Tree = LOOPHOLE[bh]; LookupC: CallTree = BEGIN b: Tree = LOOPHOLE[bh]; eR: BTree2Defs.EntryR; IF (seen ← seen + 1) = 1 THEN RETURN[more: TRUE]; eR ← LookupP[p, i]; IF ((LENGTH[eR.v] > LENGTH[e.v]) OR (LENGTH[eR.k] > LENGTH[e.k])) THEN SIGNAL EntryTooSmall; Copy[from: eR.v, to: e.v]; Copy[from: eR.k, to: e.k]; e.k ← DESCRIPTOR[BASE[e.k], LENGTH[eR.k]]; e.v ← DESCRIPTOR[BASE[e.v], LENGTH[eR.v]]; RETURN[more: FALSE]; END; IF CheckBTreeRegistered[bcheck].inValidTree THEN ERROR InvalidTree; IF bcheck.version # Version THEN Alarm[]; StartEnumerate[bh, e.k, LookupC]; IF bcheck.version # Version THEN Alarm[]; END; Delete: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle, key: BTreeDefs.Desc] = BEGIN bcheck: Tree = LOOPHOLE[bh]; DeleteC: CallTree = BEGIN b: Tree = LOOPHOLE[bh]; IF b.AreTheyEqual[key, LookupP[p, i].k] THEN BEGIN DeleteP[p, i]; IF i = 0 THEN p.rekey ← TRUE; b.entries ← b.entries - 1; b.dirtyTree ← TRUE; END; RETURN[more: FALSE]; END; IF CheckBTreeRegistered[bcheck].inValidTree THEN ERROR InvalidTree; IF bcheck.version # Version THEN Alarm[]; IF LENGTH[key] # 0 THEN StartEnumerate[bh, key, DeleteC]; IF bcheck.version # Version THEN Alarm[]; END; EnumerateFrom: PUBLIC PROCEDURE [ bh: BTreeDefs.BTreeHandle, key: BTreeDefs.Desc, c: BTreeDefs.Call] = BEGIN b: Tree = LOOPHOLE[bh]; seen: CARDINAL ← 0; EnumC: CallTree = BEGIN eR: BTree2Defs.EntryR ← LookupP[p, i]; dirty: BOOLEAN; seen ← seen + 1; IF ((seen = 1) AND (~b.AreTheyEqual[key, eR.k])) THEN RETURN[more: TRUE]; [more, dirty] ← c[ eR.k, eR.v ! UNWIND => IF (NOT Juniper) THEN b.sawUserUnwind ← TRUE]; IF dirty THEN BEGIN p.dirty ← TRUE; IF i = 0 THEN p.rekey ← TRUE; b.dirtyTree ← TRUE; END; RETURN[more]; END; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; IF b.version # Version THEN Alarm[]; StartEnumerate[bh, key, EnumC]; IF b.version # Version THEN Alarm[]; END; StartEnumerate: PROCEDURE [ bh: BTreeDefs.BTreeHandle, key: BTreeDefs.Desc, c: CallTree] = BEGIN b: Tree = LOOPHOLE[bh]; BEGIN ENABLE UNWIND => IF NOT b.sawUserUnwind THEN MarkBTreeInvalidInRegistry[b] ELSE b.sawUserUnwind ← FALSE; this: Page ← SwapInPage[b, b.rootPA, b.depth]; brother: Page ← NIL; checkT: BOOLEAN = ((checking >= dontAsk) AND (LENGTH[key] > 0)); GetPageForSplit: PROCEDURE RETURNS [Page] = BEGIN RETURN[brother ← SwapInPage[b, nullPA, this.level]]; END; IF checkT THEN CheckTree[bh]; BEGIN ENABLE UNWIND => BEGIN FreePageDueToUnwind[this]; IF brother # NIL THEN FreePageDueToUnwind[brother]; END; [] ← EnumerateFromTree[ this, key, c ! BTree2Defs.GetAnother => RESUME [GetPageForSplit[].ca]; TwoKeysNonLeafSplit => RESUME [GetPageForSplit[]]; BTree2Defs.Merger => RESUME [NIL, TRUE]]; END; IF brother # NIL THEN -- add a new level. BEGIN brother.dirty ← TRUE; BuildARoot[b, brother ! UNWIND => FreePageDueToUnwind[this]]; END; IF ((ItemsP[this] = 1) AND (b.depth # 0)) THEN -- remove a level. BEGIN b.rootPA ← LookupPA[this, 0]; this.deleted ← TRUE; b.depth ← b.depth - 1; b.dirtyTree ← TRUE; END; SwapOutPage[this]; IF b.version # Version THEN Alarm[]; IF checkT THEN CheckTree[bh]; END; END; EnumerateFromTree: PROCEDURE [this: Page, key: BTreeDefs.Desc, c: CallTree] RETURNS [go: BOOLEAN] = BEGIN index: BTree2Defs.Index; son, sonssib, right: Page; onRight, reallySplit: BOOLEAN; end: CARDINAL ← ItemsP[this]; low: NN = IF this.level > 0 THEN this.level - 1 ELSE 0; b: Tree = this.b; delta: NN; GetPageForMerge: PROCEDURE RETURNS [Page] = BEGIN onRight ← (end > index + 1); -- get the page to the right of the son unless the son is already our rightmost descendent. delta ← IF onRight THEN 1 ELSE -1; IF ((index = 0) AND (NOT onRight)) THEN RETURN[NIL]; sonssib ← SwapInPage[b, LookupPA[this, index + delta], low]; (right ← IF onRight THEN sonssib ELSE son).rekey ← TRUE; RETURN[sonssib]; END; GetPageForSplit: PROCEDURE RETURNS [Page] = BEGIN reallySplit ← TRUE; RETURN[sonssib ← SwapInPage[b, nullPA, low]]; END; IF b.version # Version THEN Alarm[]; FOR index ← FindIndexOnPage[this, key], index + 1 UNTIL index >= end DO IF this.level = 0 THEN [go] ← c[this, index] ELSE BEGIN sonssib ← NIL; reallySplit ← FALSE; delta ← 0; son ← SwapInPage[b, LookupPA[this, index], low]; BEGIN ENABLE UNWIND => BEGIN FreePageDueToUnwind[son]; IF sonssib # NIL THEN FreePageDueToUnwind[sonssib]; END; [go] ← EnumerateFromTree[ son, key, c ! BTree2Defs.GetAnother => RESUME [GetPageForSplit[].ca]; BTree2Defs.Merger => BEGIN tempPage: Page ← GetPageForMerge[]; RESUME [(IF tempPage = NIL THEN NIL ELSE tempPage.ca), onRight]; END; BTree2Defs.DeletePage => BEGIN right.deleted ← TRUE; RESUME ; END; TwoKeysNonLeafSplit => RESUME [GetPageForSplit[]]]; IF sonssib # NIL THEN BEGIN -- if we have two keys to twitch, we must avoid causing two inter-page actions at the next higher level, as the routine is not expecting more than one. Therefore the first key twitch can cause a split, and if so the second is not allowed to merge or balance. Also the first cannot merge or balance as the second may want to split. twoKeysToEnter: BOOLEAN ← ((son.deleted OR son.rekey) AND (sonssib.deleted OR sonssib.rekey)); split: Page ← NIL; sonssib.dirty ← TRUE; ChangeKeys[ this, index + delta, sonssib, (NOT reallySplit) ! BTree2Defs.GetAnother => IF twoKeysToEnter THEN BEGIN split ← SIGNAL TwoKeysNonLeafSplit; RESUME [split.ca]; END; BTree2Defs.Merger => IF twoKeysToEnter THEN RESUME [NIL, TRUE]]; IF split # NIL THEN BEGIN t: INTEGER ← index - ItemsP[this]; IF t >= 0 THEN BEGIN index ← t; this ← split; END; END; ChangeKeys[ this, index, son, TRUE ! BTree2Defs.Merger => IF ((twoKeysToEnter) AND (split # NIL)) THEN RESUME [NIL, TRUE]]; END ELSE IF son.rekey THEN ChangeKeys[this, index, son, TRUE]; END; SwapOutPage[sonssib ! UNWIND => FreePageDueToUnwind[son]; ]; SwapOutPage[son]; END; IF NOT go THEN EXIT; ENDLOOP; IF b.version # Version THEN Alarm[]; END; ChangeKeys: PROCEDURE [ this: Page, i: BTree2Defs.Index, son: Page, replace: BOOLEAN] = BEGIN eR: BTree2Defs.EntryR; this.dirty ← TRUE; IF ((i = 0) AND (son.deleted OR son.rekey)) THEN this.rekey ← TRUE; IF son.deleted THEN DeleteP[this, i] ELSE IF son.rekey THEN BEGIN son.rekey ← FALSE; eR ← LookupP[son, 0]; eR.v ← DESCRIPTOR[@son.pa, 1]; IF replace THEN BTree2Defs.Replace[this.ca, i, @eR] ELSE InsertP[this, i, @eR]; END; END; FindIndexOnPage: PROCEDURE [p: Page, key: BTreeDefs.Desc] RETURNS [m: BTree2Defs.Index] = BEGIN GreaterOrEqual: BTreeDefs.TestKeys ← p.b.IsFirstGreaterOrEqual; numItemsOnPage: BTree2Defs.Index ← ItemsP[p]; h: BTree2Defs.Index ← m ← 128; t: BOOLEAN; DO t ← IF (m >= numItemsOnPage) THEN FALSE ELSE GreaterOrEqual[key, LookupP[p, m].k]; h ← h / 2; IF h = 0 THEN EXIT ELSE m ← IF t THEN m + h ELSE m - h; ENDLOOP; IF NOT t THEN m ← m - 1; END; -- Does it matter that for two empty args this returns TRUE? MyIsFirstGreaterOrEqual: BTreeDefs.TestKeys = BEGIN i: CARDINAL; t: CARDINAL = MIN[LENGTH[b], LENGTH[a]]; g: INTEGER ← 0; FOR i IN [0..t) UNTIL ((g ← a[i] - b[i]) # 0) DO ENDLOOP; RETURN[((g > 0) OR ((g = 0) AND (LENGTH[a] >= LENGTH[b])))]; END; MyAreTheyEqual: BTreeDefs.TestKeys = BEGIN i: CARDINAL; t: CARDINAL = LENGTH[a]; IF LENGTH[b] # t THEN RETURN[FALSE]; FOR i IN [0..t) DO IF a[i] # b[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; END; NullProc: PUBLIC BTreeDefs.TestKeys = BEGIN Alarm[]; RETURN[TRUE]; -- keep compiler happy. END; Copy: PROCEDURE [from: BTreeDefs.Desc, to: BTreeDefs.Desc] = BEGIN i, l: CARDINAL; l ← MIN[LENGTH[from], LENGTH[to]]; FOR i IN [0..l) DO to[i] ← from[i]; ENDLOOP; END; SwapInPage: PROCEDURE [b: Tree, p: NN, l: CARDINAL] RETURNS [bp: Page] = BEGIN place: BTreeSupportDefs.PageHandle ← [NIL]; new: BOOLEAN ← (p = nullPA); reuse: BOOLEAN ← (new AND (b.freePA # 0)); IF new THEN BEGIN p ← IF reuse THEN b.freePA ELSE b.lastPA ← b.lastPA + 1; b.dirtyTree ← TRUE; END; place ← (IF ((NOT new) OR reuse) THEN BTreeSupportDefs.ReadPage ELSE BTreeSupportDefs.UsePage)[b.file, p - 1]; bp ← BTreeSupportDefs.AllocateWords[SIZE[BTreePageRecord]]; bp.pa ← p; bp.b ← b; bp.ca ← @bp.c; bp.dirty ← FALSE; bp.c ← DESCRIPTOR[place, b.pageS]; bp.level ← l; bp.rekey ← new; bp.deleted ← FALSE; IF reuse THEN b.freePA ← bp.ca[0]; IF new THEN BEGIN bp.dirty ← FALSE; BTree2Defs.Initialize[bp.ca]; END; IF checking >= moderate THEN Check[bp]; END; SwapOutPage: PROCEDURE [p: Page] = BEGIN b: Tree; temp: BTreeSupportDefs.PageHandle; IF p = NIL THEN RETURN; IF checking >= moderate THEN Check[p]; b ← p.b; temp ← LOOPHOLE[BASE[p.c]]; IF p.deleted THEN BEGIN p.ca[0] ← b.freePA; b.freePA ← p.pa; b.dirtyTree ← p.dirty ← TRUE; END; IF p.dirty THEN BTreeSupportDefs.WritePage[ p.b.file, p.pa - 1, temp ! UNWIND => BEGIN BTreeSupportDefs.ReleasePage[temp]; BTreeSupportDefs.FreeWords[p]; END] ELSE BTreeSupportDefs.ReleasePage[temp]; BTreeSupportDefs.FreeWords[p]; END; FreePageDueToUnwind: PROCEDURE [p: Page] = BEGIN temp: BTreeSupportDefs.PageHandle ← LOOPHOLE[BASE[p.c]]; --this is the only way BTreeSupportDefs.ReleasePage[temp]; -- the compiler will accept this. BTreeSupportDefs.FreeWords[p]; END; BuildARoot: PROCEDURE [b: Tree, brother: Page] = BEGIN eR: BTree2Defs.EntryR; root: Page ← SwapInPage[ b, nullPA, b.depth ← b.depth + 1 ! UNWIND => IF brother # NIL THEN FreePageDueToUnwind[brother]]; IF brother # NIL THEN BEGIN eR ← LookupP[brother, 0]; eR.v ← DESCRIPTOR[@brother.pa, 1]; InsertP[root, 177777B, @eR]; SwapOutPage[brother ! UNWIND => FreePageDueToUnwind[root]]; END; eR ← [DESCRIPTOR[NIL, 0], DESCRIPTOR[@b.rootPA, 1]]; InsertP[root, 177777B, @eR]; b.rootPA ← root.pa; b.dirtyTree ← TRUE; SwapOutPage[root]; END; LookupPA: PROCEDURE [p: Page, i: BTree2Defs.Index] RETURNS [NN] = BEGIN RETURN[LookupP[p, i].v[0]]; END; InsertP: PROCEDURE [p: Page, i: BTree2Defs.Index, e: BTree2Defs.Entry] = BEGIN p.dirty ← TRUE; BTree2Defs.Insert[p.ca, i, e]; END; ReplaceP: PROCEDURE [p: Page, i: BTree2Defs.Index, e: BTree2Defs.Entry] = BEGIN p.dirty ← TRUE; BTree2Defs.Replace[p.ca, i, e]; END; LookupP: PROCEDURE [p: Page, i: BTree2Defs.Index] RETURNS [BTree2Defs.EntryR] = BEGIN RETURN[BTree2Defs.Lookup[p.ca, i]]; END; DeleteP: PROCEDURE [p: Page, i: BTree2Defs.Index] = BEGIN p.dirty ← TRUE; BTree2Defs.Delete[p.ca, i]; END; ItemsP: PROCEDURE [p: Page] RETURNS [CARDINAL] = BEGIN RETURN[BTree2Defs.Items[p.ca]]; END; Check: PROCEDURE [p: Page] = BEGIN b: Tree = p.b; i: NN; n: NN = ItemsP[p]; e: BTree2Defs.EntryR; thisIsPageOne: BOOLEAN = (p = @b.self); IF b.version # Version THEN Alarm[]; SELECT thisIsPageOne FROM TRUE => IF p.pa # 1 THEN Malformed; ENDCASE => IF p.pa NOT IN [2..b.lastPA] THEN Malformed; IF ((p.ca # @p.c) OR (LENGTH[p.c] # b.pageS)) THEN Malformed; IF ((p.level > b.depth) AND (NOT p.deleted)) THEN Malformed; IF ((b.rootPA NOT IN [2..b.lastPA]) AND ~(b.rootPA = nullPA AND p.level = b.depth)) THEN Malformed; IF ((b.freePA > b.lastPA) OR (b.freePA = 1)) THEN Malformed; IF n >= 2 THEN IF b.IsFirstGreaterOrEqual[LookupP[p, 0].k, LookupP[p, n - 1].k] THEN Malformed; -- Spot check IF n >= 4 THEN IF b.AreTheyEqual[LookupP[p, n / 2].k, LookupP[p, n / 2 - 1].k] THEN Malformed; -- Spot check IF p.level > 0 THEN FOR i IN [0..n) DO e ← LookupP[p, i]; IF ((LENGTH[e.v] # 1) OR (e.v[0] NOT IN [2..b.lastPA])) THEN Malformed; IF ((LENGTH[e.k] = 0) AND (i > 0)) THEN Malformed; ENDLOOP; END; CheckTree: PUBLIC PROCEDURE [bh: BTreeDefs.BTreeHandle] = BEGIN b: Tree = LOOPHOLE[bh]; seen: CARDINAL ← 0; was: CheckAmount = checking; previous: ARRAY [0..32) OF UNSPECIFIED; d: BTreeDefs.Desc ← DESCRIPTOR[NIL, 0]; CheckC: CallTree = BEGIN k: BTreeDefs.Desc ← LookupP[p, i].k; SELECT LENGTH[k] FROM > LENGTH[previous] => Malformed; > 0 => BEGIN seen ← seen + 1; IF b.IsFirstGreaterOrEqual[d, k] THEN Malformed; END; ENDCASE; d ← DESCRIPTOR[@previous, LENGTH[k]]; FOR i IN [0..LENGTH[k]) DO d[i] ← k[i]; ENDLOOP; RETURN[more: TRUE]; END; IF CheckBTreeRegistered[b].inValidTree THEN ERROR InvalidTree; checking ← moderate; StartEnumerate[bh, d, CheckC]; IF seen # b.entries THEN Malformed; checking ← was; END; END. Edit Log RTE: March 14, 1980 5:47 PM: KK: edited in Will's second fix for the split page - key problem. Comment: March 28, 1980 7:13 PM: KK: changed the format of this program so that I could read it. RTE: April 15, 1980 3:04 PM: KK: fixes in ChangeKeys and EnumFromTree for "two operations at once" bugs and delete not propagating the key bug. RTE: May 1, 1980 5:12 PM: KK: insert didn't call replace for identical keys. Comment: May 9, 1980 2:54 PM: KK: began changes to reflect changes to interface and removing caching. RTE: September 8, 1980 1:14 PM: KK: Mike Schroeder's fix: UNWIND in Enumerate. RTE: September 25, 1980 4:20 PM: KK: Unwind catches for transaction reset signals. Also take out the two bV and bB things. Comment: October 17, 1980 11:51 AM: KK: added checks on version to catch bugs. Comment: October 21, 1980 7:02 PM: KK: added registry of btrees. RTE: October 27, 1980 12:40 PM: KK: bug fix plus cleanup to above. RTE: November 4, 1980 3:54 PM: KK: changed alarms to InvalidTree errors. Also userUnwind fix.