-- File: DBStorageGroupScanImpl.mesa -- This module exports group-related stuff to DBStorage. -- Last edited by: -- MBrown on December 2, 1982 3:17 pm -- Cattell on November 2, 1983 3:19 pm -- Willie-Sue on June 30, 1982 4:49 pm DIRECTORY DBCache USING [CacheHandle], DBCommon USING [GetDebugStream], DBEnvironment, DBSegment, DBStorage USING[FieldHandle, FirstLast, MaxSystemTupleID, TupleHandle], DBStorageField USING[ CheckFieldHandleType, GroupIDOfField], DBStorageGroup USING[GroupList, GroupListFromTuple, ReadGroupField, WriteGroupField, WriteHeadField, CreateHeadEntry, DestroyHeadEntry], DBStorageConcrete USING[GroupScanObject], DBStorageGroupScan USING[], DBStoragePrivate USING[TupleFromTID], DBStorageTID, DBStorageTuple USING[ConsTupleObject, EqualTuple, IsValidTuple, InvalidateTuple, NullTupleHandle, PrintTupleObject], IO; DBStorageGroupScanImpl: PROGRAM IMPORTS DBCommon, DBEnvironment, DBSegment, DBStorageField, DBStorageGroup, DBStoragePrivate, DBStorageTuple, IO EXPORTS DBStorage, DBStorageGroupScan = BEGIN OPEN IO, DBEnvironment, DBStorageGroup, DBStorageTuple; TID: TYPE = DBStorageTID.TID; NullTID: TID = DBStorageTID.NullTID; TupleHandle: TYPE = DBStorage.TupleHandle; -- Type exported to DBStorage GroupScanObject: PUBLIC TYPE = DBStorageConcrete.GroupScanObject; GroupScanHandle: TYPE = REF GroupScanObject; -- Module global state activeGroupScanList: GroupScanHandle; -- 1st item on list is a permanent header node nullTupleHandle: TupleHandle; Init: PUBLIC PROC = { activeGroupScanList ← NEW[GroupScanObject ← [ tidFieldHandle: NIL, headOfGroup: NIL]]; nullTupleHandle ← DBStorageTuple.NullTupleHandle[]; }; ReadTID: PUBLIC PROC[x: TupleHandle, f: DBStorage.FieldHandle] RETURNS[TupleHandle] = { -- If x belongs to an f-group, ReadTID returns the head of the group; otherwise it --returns NIL. RETURN[ReadGroupField[x, f, headTID]]; };--ReadTID WriteTID: PUBLIC PROC[x: TupleHandle, r: GroupScanHandle] = { -- Makes tuple x join the group specified by r at r's current position (i.e. between -- the tuple just read and the next tuple that would be read by NextInGroup). A -- NextInGroup following WriteTID returns the tuple following x; a PrevInGroup -- returns x itself. -- Note that: (1) if x's field y had a non-NIL value before the WriteTID call, then -- WriteTID has the effect of deleting x from some group (using WriteTIDNIL below), -- (2) WriteTID cannot delete a tuple from one group without inserting it into -- another, so WriteTIDNil below is necessary for that case, and (3) inserting or -- deleting a tuple from a group requires the storage level to check all outstanding -- GroupScanHandles to assure that they stay consistent with the deletion. -- For now, this works only when inserting at the front... f: DBStorage.FieldHandle ← r.tidFieldHandle; WriteTIDNil[x, f]; WriteGroupField[x, f, headTID, r.headOfGroup]; SELECT TRUE FROM (r.prevInScan=NIL) AND (r.nextInScan=NIL) => BEGIN --x is only tuple in group CreateHeadEntry[r.headOfGroup, f]; WriteHeadField[r.headOfGroup, f, firstTID, x]; WriteHeadField[r.headOfGroup, f, lastTID, x]; WriteGroupField[x, f, prevTID, nullTupleHandle]; WriteGroupField[x, f, nextTID, nullTupleHandle]; END; (r.prevInScan=NIL) AND (r.nextInScan#NIL) => BEGIN --x is at front of group WriteHeadField[r.headOfGroup, f, firstTID, x]; WriteGroupField[x, f, prevTID, nullTupleHandle]; WriteGroupField[x, f, nextTID, r.nextInScan]; WriteGroupField[r.nextInScan, f, prevTID, x]; END; (r.prevInScan#NIL) AND (r.nextInScan=NIL) => BEGIN --x is at rear of group WriteHeadField[r.headOfGroup, f, lastTID, x]; WriteGroupField[x, f, nextTID, nullTupleHandle]; WriteGroupField[x, f, prevTID, r.prevInScan]; WriteGroupField[r.prevInScan, f, nextTID, x]; END; (r.prevInScan#NIL) AND (r.nextInScan#NIL) => BEGIN --x is in middle of group WriteGroupField[x, f, nextTID, r.nextInScan]; WriteGroupField[r.nextInScan, f, prevTID, x]; WriteGroupField[x, f, prevTID, r.prevInScan]; WriteGroupField[r.prevInScan, f, nextTID, x]; END; ENDCASE => ERROR; BEGIN --fix up active scans that are in identical position to scan (others are unaffected) FOR p: GroupScanHandle ← activeGroupScanList.link, p.link UNTIL p = NIL DO IF (DBStorageField.GroupIDOfField[p.tidFieldHandle] # DBStorageField.GroupIDOfField[r.tidFieldHandle]) OR (~EqualTuple[p.headOfGroup,r.headOfGroup]) OR (~EqualTuple[p.prevInScan,r.prevInScan]) THEN LOOP; -- p is effectively identical to scan p.prevInScan ← x; -- no need for explicit free here anymore ... ENDLOOP; END; };--WriteTID WriteTIDNil: PUBLIC PROC[x: TupleHandle, f: DBStorage.FieldHandle] = { -- Makes x's TID field point to the null tuple, i.e. removes x from whatever f-group -- it currently belongs. head, prev, next: TupleHandle; head ← ReadGroupField[x, f, headTID]; IF head = NIL THEN RETURN; prev ← ReadGroupField[x, f, prevTID]; next ← ReadGroupField[x, f, nextTID]; WriteGroupField[x, f, headTID, nullTupleHandle]; WriteGroupField[x, f, prevTID, nullTupleHandle]; --redundant WriteGroupField[x, f, nextTID, nullTupleHandle]; --redundant SELECT TRUE FROM (prev=NIL) AND (next=NIL) => BEGIN --x was only tuple in group DestroyHeadEntry[head, f]; END; (prev=NIL) AND (next#NIL) => BEGIN --x was first tuple in group WriteGroupField[next, f, prevTID, nullTupleHandle]; WriteHeadField[head, f, firstTID, next]; END; (prev#NIL) AND (next=NIL) => BEGIN --x was last tuple in group WriteGroupField[prev, f, nextTID, nullTupleHandle]; WriteHeadField[head, f, lastTID, prev]; END; (prev#NIL) AND (next#NIL) => BEGIN --x was in middle of group WriteGroupField[prev, f, nextTID, next]; WriteGroupField[next, f, prevTID, prev]; END; ENDCASE => ERROR; -- head is free now BEGIN --fix up active scans that contain x (others are unaffected) FOR p: GroupScanHandle ← activeGroupScanList.link, p.link UNTIL p = NIL DO IF DBStorageField.GroupIDOfField[p.tidFieldHandle] # DBStorageField.GroupIDOfField[f] THEN LOOP; IF EqualTuple[p.prevInScan,x] THEN p.prevInScan ← prev; IF EqualTuple[p.nextInScan,x] THEN p.nextInScan ← next; ENDLOOP; END; -- prev, next are free now };--WriteTIDNil GetGroupIDs: PUBLIC PROC[x: TupleHandle] RETURNS[LIST OF TupleHandle] = { -- Returns a list containing all GroupIDs g such that z is the head of a g-group. -- The Tuple level is responsible for mapping from GroupIDs to FieldHandles, so that -- OpenScanGroup can be called when necessary. groupList: LONG DESCRIPTOR FOR DBStorageGroup.GroupList; cacheHint: DBCache.CacheHandle; result: LIST OF TupleHandle; [groupList,cacheHint] ← GroupListFromTuple[x]; IF LENGTH[groupList] = 0 THEN result ← NIL ELSE { newCons, lastCons: LIST OF TupleHandle; FOR i: CARDINAL IN [0..LENGTH[groupList]) DO newCons ← LIST[ IF groupList[i].groupID <= DBStorage.MaxSystemTupleID THEN DBStoragePrivate.TupleFromTID[groupList[i].groupID] ELSE DBStorageTuple.ConsTupleObject[tid: groupList[i].groupID, cacheHint: NIL]]; -- Append the cell just created to the list we're growing. IF i = 0 THEN result ← newCons ELSE lastCons.rest ← newCons; lastCons ← newCons; ENDLOOP; };--IF DBSegment.UnlockPage[cacheHint]; RETURN[result]; };--GetGroupIDs OpenScanGroup: PUBLIC PROC[x: TupleHandle, f: DBStorage.FieldHandle, start: DBStorage.FirstLast] RETURNS[GroupScanHandle] = { -- Returns a GroupScanHandle positioned before the first (or after the last) tuple -- of the f-group that has tuple x as head (first or last depending upon the value of -- start). Returns a non-NIL GroupScanHandle even if there is no group (since the -- scan handle is the only way to CREATE a group). groupList: LONG DESCRIPTOR FOR DBStorageGroup.GroupList; cacheHint: DBCache.CacheHandle; result: GroupScanHandle; DBStorageField.CheckFieldHandleType[f, Group]; result ← Alloc[f, x]; result.link ← activeGroupScanList.link; activeGroupScanList.link ← result; [groupList, cacheHint] ← DBStorageGroup.GroupListFromTuple[x]; IF LENGTH[groupList] # 0 THEN BEGIN FOR i: CARDINAL IN [0..LENGTH[groupList]) DO IF groupList[i].groupID = DBStorageField.GroupIDOfField[f] THEN GOTO FoundIt; REPEAT FoundIt => SELECT start FROM First => result.nextInScan← DBStorageTuple.ConsTupleObject[groupList[i].firstTID, NIL]; Last => result.prevInScan← DBStorageTuple.ConsTupleObject[groupList[i].lastTID, NIL]; ENDCASE => ERROR; FINISHED => NULL; -- this is ok, the scan is just empty ENDLOOP; END;--IF DBSegment.UnlockPage[cacheHint]; RETURN[result]; };--OpenScanGroup Alloc: PROC [f: DBStorage.FieldHandle, x: TupleHandle] RETURNS [GroupScanHandle] = INLINE { RETURN[NEW[GroupScanObject ← [ tidFieldHandle: f, headOfGroup: x, prevInScan: NIL, nextInScan: NIL, link: NIL]]] }; NextInGroup: PUBLIC PROC[r: GroupScanHandle] RETURNS[TupleHandle] = { -- Returns the tuple x immediately following the position r in a group, and advances -- r one position. Returns NIL if r was already positioned after the last tuple in -- the group. result: TupleHandle ← NIL; IF r=NIL THEN RETURN[NIL]; -- convenience feature IF r.tidFieldHandle = NIL THEN ERROR InternalError; -- InvalidGroupScan IF r.nextInScan#NIL THEN { r.prevInScan ← result ← r.nextInScan; r.nextInScan ← DBStorageGroup.ReadGroupField[result, r.tidFieldHandle, nextTID]; };--IF RETURN[result]; };--NextInGroup PrevInGroup: PUBLIC PROC[r: GroupScanHandle] RETURNS[TupleHandle] = { -- Returns the tuple x immediately preceding the position r in a group, and moves r -- one position backwards. Returns NIL if r was already positioned before the first -- tuple in the group. result: TupleHandle ← NIL; IF r=NIL THEN RETURN[NIL]; -- convenience feature IF r.tidFieldHandle = NIL THEN ERROR InternalError; -- InvalidGroupScan IF r.prevInScan # NIL THEN { r.nextInScan ← result ← r.prevInScan; r.prevInScan ← DBStorageGroup.ReadGroupField[result, r.tidFieldHandle, prevTID]; };--IF RETURN[result]; };--PrevInGroup CloseScanGroup: PUBLIC PROC[r: GroupScanHandle] = { FOR scanPred: GroupScanHandle ← activeGroupScanList, scanPred.link UNTIL scanPred=NIL DO IF scanPred.link=r THEN { scanPred.link ← r.link; r.link ← NIL; FreeContents[r]; RETURN; }; REPEAT FINISHED => ERROR DBEnvironment.InternalError; -- [GroupScanNotFound]; ENDLOOP; };--CloseScanGroup CallAfterFinishTransaction: PUBLIC PROC [] = { rPrev: GroupScanHandle ← activeGroupScanList; UNTIL rPrev.link = NIL DO r: GroupScanHandle ← rPrev.link; IF NOT DBStorageTuple.IsValidTuple[r.headOfGroup] THEN { DBStorageTuple.InvalidateTuple[r.prevInScan]; DBStorageTuple.InvalidateTuple[r.nextInScan]; FreeContents[r]; rPrev.link ← r.link; r.link ← NIL; } ELSE rPrev ← r; ENDLOOP; }; FreeContents: PROC[r: GroupScanHandle] = { -- Frees all objects that are dependent on r. works even if r is invalid, i.e. FreeContents[r] -- has been done before. r.tidFieldHandle ← NIL; r.headOfGroup ← r.prevInScan ← r.nextInScan ← NIL; };--FreeContents CheckState: PUBLIC PROC[doPrinting: BOOLEAN] = { -- There isn't really much to check, except that the lists are well-formed. -- if this runs forever, one isn't. activeGroupScanListLen: CARDINAL ← 0; FOR r: GroupScanHandle ← activeGroupScanList.link, r.link UNTIL r=NIL DO activeGroupScanListLen ← activeGroupScanListLen + 1; DBStorageField.CheckFieldHandleType[r.tidFieldHandle, Group]; IF r.headOfGroup = NIL THEN ERROR; ENDLOOP; IF doPrinting THEN { DBCommon.GetDebugStream[].PutF["list of active group scans contains %d elements*n", card[activeGroupScanListLen]]; FOR r: GroupScanHandle ← activeGroupScanList, r.link UNTIL r=NIL DO PrintGroupScanObject[r]; ENDLOOP; DBCommon.GetDebugStream[].PutF["*n"]; };--IF };--CheckState PrintGroupScanObject: PROC[scan: GroupScanHandle] = { pscan: POINTER TO GroupScanHandle = @scan; debugStream: IO.STREAM← DBCommon.GetDebugStream[]; debugStream.PutF["groupScanHdl: %12bB, tidFieldHdl: ", card[LOOPHOLE[pscan, CARDINAL]]]; --DBStorageField.PrintFieldObject[scan.tidFieldHandle]; debugStream.PutF["*n headOfGroup: "]; DBStorageTuple.PrintTupleObject[scan.headOfGroup]; debugStream.PutF["*n prevInScan: "]; DBStorageTuple.PrintTupleObject[scan.prevInScan]; debugStream.PutF["*n nextInScan: "]; DBStorageTuple.PrintTupleObject[scan.nextInScan]; debugStream.PutF["*n"]; };--PrintGroupScanObject END.--DBStorageGroupScanImpl CHANGE LOG Created by MBrown on June 17, 1980 1:00 PM -- Moved code here from StorageImplB. Changed by MBrown on June 20, 1980 4:19 PM -- Added explicit management of free GroupScanObjects. Changed by MBrown on July 23, 1980 10:10 PM -- FirstLast changed from {First, Last} to {first, last}. Changed by MBrown on August 4, 1980 11:50 PM -- Added NoticeCloseDatabase. Changed ListofGroupID to ListOfGroupID. Changed by MBrown on August 20, 1980 9:15 PM -- Added CheckState. Changed by MBrown on September 12, 1980 2:13 PM -- Added Finalize. Changed by MBrown on September 14, 1980 11:27 PM -- NullTupleObject went away during Finalize! Now it is kept in the global frame, which requires us --to export TupleHandle, etc... Changed by MBrown on September 26, 1980 12:33 PM -- Converted to new DBException. Changed by MBrown on November 12, 1980 4:09 PM -- In NoticeCloseDatabase, set activeGroupScanList ← NIL after deallocation. --This is a hack; it leaves garbage around, and doing a CloseScanGroup on a --deleted scan will cause InternalBug[GroupScanNotFound] to be generated. Changed by MBrown on December 11, 1980 2:26 PM -- Undid the above hack, and modified Finalize to not raise any signals. Made NextInGroup --and PrevInGroup ERROR BadOperation[InvalidGroupScan] when invoked on a group scan with --r.tidFieldHandle = NIL. Changed by MBrown on December 20, 1980 6:04 PM -- Added cleanup code in NextInGroup and PrevInGroup, to avoid error in finalization. Changed by MBrown on February 27, 1981 3:58 PM -- Use zone for storage allocation. Changed by MBrown on 9-Jun-81 18:13:50 -- Converting to use collectable storage for GroupScanObject, TupleObject. Changed by Willie-Sue on June 24, 1982 12:19 pm -- IOStream => IO Changed by Willie-Sue on June 30, 1982 4:49 pm -- PrivateIO.DebugStream => DBCommon.GetDebugStream[] Changed by MBrown on November 30, 1982 10:07 pm -- Changes for new segment scheme.