-- File: DBStorageImplC.mesa
-- Last edited by:
-- MBrown on December 16, 1982 2:45 pm
-- Cattell on November 8, 1983 1:13 pm

DIRECTORY
DBCache USING[CacheHandle],
DBCommon USING[DBPage, Segment, NullDBPage, NotOnList, WordsPerPage],
DBEnvironment,
DBSegment USING[ReadPage, WritePage, WriteLockedPage, AllocPage, FreePage, UnlockPage,
SegmentIDFromDBPage, SegmentIDFromSegment],
DBStats,
DBStorage USING[],
DBStorageConcrete USING[SystemTuplesetObject],
DBStorageExtTuple USING[TupleBody, SizeOfNullTuple],
DBStorageField USING[TuplesetFieldHandle],
DBStorageGroup USING[GroupListEntry],
DBStoragePagetags USING[AssertPageIsTuplePage, AssertPageIsSystemTuplePage,
AssertPageIsAnyTuplePage, Tuple, SystemTuple],
DBStoragePrivate USING[GetNWordBase],
DBStorageString USING[SizeOfNullLString],
DBStorageTSDict USING[TSDict, TSDictEntry, TSDictSlotIndex, SizeOfNullTSDict,
SizeOfInitialTSDict],
DBStorageTID USING[TID, ConsTID, DecomposeTID],
DBStorageTuple USING[ConsTupleObject, InvalidateMatchingTuples],
DBStorageTupleset USING[TuplesetObject],
DBStorageTuplesetScan USING[NoticeDeletion],
DBStorageVec USING[FreeType, VecPage, TagOfPage, WordsLeftOnPage, InitializeVecPage, VecHeader,
LengthOfVec, AllocVec, FreeVec, ModifyVec, VecOfSlot, TypeOfSlot, SetTypeOfSlot, Slot,
HighSlotIndexOfPage],
DBStorageVectags USING[LString, MaxTuplesetPerPage, TSDictType],
DBTuplesConcrete USING [TupleObject],
PrincOpsUtils USING[LongCOPY];

DBStorageImplC: PROGRAM
IMPORTS
DBEnvironment,
DBSegment,
DBStats,
DBStorageField,
DBStoragePagetags,
DBStoragePrivate,
DBStorageTID,
DBStorageTuple,
DBStorageTuplesetScan,
DBStorageVec,
PrincOpsUtils
EXPORTS
DBEnvironment,
DBStorage,
DBStorageTSDict
= BEGIN OPEN DBCommon, DBEnvironment, DBStorageTID;

-- This module exports part of DBStorage: tuple creation/destruction, tupleset scans.
--It also exports ops to DBStorageTSDict. It exports DBStorage.TupleObject
--in order to gain direct access to tid and cacheHint for reading; it deals through the
--DBStorageTuple interface for writing. It also exports DBStorage.SystemTuplesetObject
--in order to perform CreateSystemPageTuple.

-- PROBLEMS, THINGS YET TO BE DONE:
-- Unimplemented: CreateTuple with colocation. DestroyTuple will require work when long
-- string values are implemented.

DoChecks: BOOLEAN = TRUE;
-- if TRUE, perform lots of redundant internal checking.
UnlikelySlotIndex: CARDINAL = LAST[CARDINAL];
UserTupleCreateThreshold: CARDINAL = DBCommon.WordsPerPage/8;
SystemTupleCreateThreshold: CARDINAL = DBCommon.WordsPerPage/4;
-- require this many free words on a page (when DONE) to create a tuple there.
AllocThreshold: CARDINAL = DBCommon.WordsPerPage/2;
-- require this many free words on a page to place on alloc list.

-- Types exported to DBStorage

TupleObject: PUBLIC TYPE = DBTuplesConcrete.TupleObject;
TupleHandle: TYPE = REF TupleObject;

SystemTuplesetObject: PUBLIC TYPE = DBStorageConcrete.SystemTuplesetObject;
SystemTuplesetHandle: TYPE = REF SystemTuplesetObject;

CreateTuple: PUBLIC PROC[x: TupleHandle--tupleset--, y: TupleHandle]
RETURNS[--newTuple-- TupleHandle] = {
-- Attempts to create a new tuple of type x "near" existing tuple y. (If y=NIL then
--this creates a new tuple of type x, according to the default algorithm for
--tupleset x.). Returns with newTuple=NIL iff this is not possible; in this case no
--tuple has been created.
-- Current limitations: y#NIL (colocation) is unimplemented.
dbPage: DBCommon.DBPage;
dbPagePtr: LONG POINTER TO DBStorageVec.VecPage; dbPageHdl: DBCache.CacheHandle ← NIL;
slotIndex: CARDINAL;
success, mustSucceed: BOOLEAN ← FALSE;
tsObjPtr: LONG POINTER TO DBStorageTupleset.TuplesetObject; tsObjHdl: DBCache.CacheHandle;
[tsObjPtr, tsObjHdl] ← DBStoragePrivate.GetNWordBase[x, DBStorageField.TuplesetFieldHandle[]];

DBStats.Inc[StorageCreateTuple];
IF y#NIL THEN ERROR Error[NotImplemented]; -- colocation is unimplemented, goes here

DO --until tuple created
IF tsObjPtr.allocList = NotOnList THEN ERROR DBEnvironment.InternalError;
IF tsObjPtr.allocList = NullDBPage THEN {
-- add a page to allocList, and set mustSucceed flag
[dbPage, dbPageHdl, dbPagePtr] ← DBSegment.AllocPage[tsObjPtr.pageAllocator.segmentID];
DBSegment.WriteLockedPage[dbPageHdl]; --about to write dbPagePtr^
InitializeTuplePage[p: dbPagePtr, tsID: tsObjPtr.searchList.tuplesetID,
pageTag: DBStoragePagetags.Tuple];
-- make page be first (and last) on allocList.
DBSegment.WriteLockedPage[tsObjHdl]; tsObjPtr.allocList ← dbPage;
{
-- link page into searchList of this tupleset
ts: TupleHandle ← DBStorageTuple.ConsTupleObject[tsObjPtr.searchList.tuplesetID, tsObjHdl];
WriteTSDictField[dbPage, ts, prev, NullDBPage];
WriteTSDictField[dbPage, ts, next, tsObjPtr.searchList.next];
WriteTSDictField[tsObjPtr.searchList.next, ts, prev, dbPage];
WriteTSDictField[NullDBPage, ts, next, dbPage];
--DBStorageTuple.FreeTupleObject[ts];
};
DBSegment.UnlockPage[dbPageHdl];
mustSucceed ← TRUE;
};--IF
-- allocList is nonempty; try to place the tuple on its first page
dbPage ← tsObjPtr.allocList;
[dbPageHdl, dbPagePtr] ← DBSegment.ReadPage[dbPage, dbPageHdl];
DBStoragePagetags.AssertPageIsTuplePage[dbPagePtr];
IF ~PageFullP[dbPagePtr, tsObjPtr] THEN
-- first page of allocList isn't too full, so try allocation
[slotIndex, success] ← TryAllocTuple[dbPagePtr, dbPageHdl, tsObjPtr.searchList.tuplesetID,
tsObjPtr.wordsForTupleFields];
IF mustSucceed AND ~success THEN ERROR DBEnvironment.InternalError;
IF ~success OR PageFullP[dbPagePtr, tsObjPtr] THEN {
-- tuple won't fit on this page, so remove it from allocList.
-- this is done AFTER tuple allocation so that if page gets full we take it off right away.
tsDict: LONG POINTER TO DBStorageTSDict.TSDict ← TSDictBaseFromPagePtr[dbPagePtr];
IF tsDict.allocLink = NotOnList THEN ERROR DBEnvironment.InternalError;
DBSegment.WriteLockedPage[tsObjHdl]; tsObjPtr.allocList ← tsDict.allocLink;
DBSegment.WriteLockedPage[dbPageHdl]; tsDict.allocLink ← NotOnList;
};--IF
DBSegment.UnlockPage[dbPageHdl];
IF success THEN EXIT;
ENDLOOP;
DBSegment.UnlockPage[tsObjHdl];
-- here slotIndex is the index of the new tuple, on page dbPage, with unlocked cache handle
--dbPageHdl.
RETURN[DBStorageTuple.ConsTupleObject[tid: ConsTID[dbPage, slotIndex], cacheHint: dbPageHdl]];
};--CreateTuple

PageFullP: PROC[dbPagePtr: LONG POINTER TO DBStorageVec.VecPage,
tsObjPtr: LONG POINTER TO DBStorageTupleset.TuplesetObject] RETURNS[BOOLEAN] = {
-- TRUE iff another tuple from ts should NOT be placed on page.
--Guaranteed to get more elaborate, and change every time we change anything...
fixedPageOverhead: CARDINAL = --VecPage overhead, including FreeSlot and FreeVec
SIZE[DBStorageVec.VecPage] + SIZE[DBStorageVec.Slot] + SIZE[DBStorageVec.VecHeader];
localTSID: CARDINAL ← 1; --this is a cheat, works only no colocation (one ts/page)
wordsForOneTuple: CARDINAL = tsObjPtr.wordsForTupleFields + SIZE[DBStorageVec.Slot] +
DBStorageExtTuple.SizeOfNullTuple +
tsObjPtr.expectedNGroups * SIZE[DBStorageGroup.GroupListEntry];
wordsInUse: CARDINAL ← fixedPageOverhead;
FOR i: CARDINAL IN [1..DBStorageVec.HighSlotIndexOfPage[dbPagePtr]] DO
slotType: CARDINAL = DBStorageVec.TypeOfSlot[dbPagePtr, i];
wordsInVec: CARDINAL = DBStorageVec.LengthOfVec[DBStorageVec.VecOfSlot[dbPagePtr,i]];
SELECT slotType FROM
IN [1..DBStorageVectags.MaxTuplesetPerPage] => {
IF slotType = localTSID THEN wordsInUse ← wordsInUse + MAX[wordsForOneTuple, wordsInVec]
ELSE ERROR;--you implemented colocation without fixing this procedure!
};
DBStorageVectags.LString, DBStorageVectags.TSDictType => {
wordsInUse ← wordsInUse + wordsInVec;
};
DBStorageVec.FreeType => { };
DBStorageVectags.TSDictType => { }; --already counted above
ENDCASE => ERROR;
ENDLOOP;
RETURN[WordsPerPage <= UserTupleCreateThreshold + wordsInUse + LOOPHOLE[(wordsForOneTuple +
tsObjPtr.nVarFields*DBStorageString.SizeOfNullLString),CARDINAL]]
};--PageFullP

SystemPageFullP: PROC[dbPagePtr: LONG POINTER TO DBStorageVec.VecPage] RETURNS[BOOLEAN] = INLINE {
-- TRUE iff another tuple from ts should NOT be placed on page.
--Guaranteed to get more elaborate...
RETURN[DBStorageVec.WordsLeftOnPage[dbPagePtr] <= SystemTupleCreateThreshold]
};--SystemPageFullP

CreateSystemPageTuple: PUBLIC PROC
[x: SystemTuplesetHandle, y: TupleHandle, s: DBCommon.Segment]
RETURNS[--newTuple-- TupleHandle] = {
-- The same as CreateTuple except that when y = NIL the new tuple is always placed
--on an otherwise empty page in segment s.

dbPage: DBPage; dbPagePtr: LONG POINTER TO DBStorageVec.VecPage;
dbPageHdl: DBCache.CacheHandle;
slotIndex: CARDINAL;
segment: DBPage; --Internal ID of segment where tuple goes.
DBStats.Inc[StorageCreateSystemTuple];
BEGIN--block for EXITS
success: BOOLEAN;
IF y # NIL THEN BEGIN
ys: REF TupleObject[stored] = NARROW[y];
[dbPage,] ← DecomposeTID[ys.tid];
[dbPageHdl, dbPagePtr] ← DBSegment.ReadPage[dbPage, ys.cacheHint];
ys.cacheHint ← dbPageHdl;
DBStoragePagetags.AssertPageIsSystemTuplePage[dbPagePtr];
IF ~SystemPageFullP[dbPagePtr] THEN BEGIN
--try to create the tuple here
[slotIndex, success] ←
TryAllocTuple[dbPagePtr, dbPageHdl, x.tuplesetID, x.wordsForTupleFields];
IF success THEN GOTO Finish;
END;--IF
-- here if colocation was requested but couldn't be done. ignore the supplied
--value of s, and force new tuple to be in same segment as y.
DBSegment.UnlockPage[dbPageHdl];
segment ← DBSegment.SegmentIDFromDBPage[dbPage]; END
ELSE BEGIN
-- y=NIL, so use segment s (this should be used for relatively few tuples)
segment ← DBSegment.SegmentIDFromSegment[s];
END;--IF
-- here if no colocation
[dbPage, dbPageHdl, dbPagePtr] ← DBSegment.AllocPage[segment];
DBSegment.WriteLockedPage[dbPageHdl];
InitializeTuplePage[dbPagePtr, x.tuplesetID, DBStoragePagetags.SystemTuple];
[slotIndex, success] ←
TryAllocTuple[dbPagePtr, dbPageHdl, x.tuplesetID, x.wordsForTupleFields];
IF success THEN GOTO Finish;
ERROR DBEnvironment.InternalError;
EXITS
Finish => BEGIN
-- here slotIndex is the index of the new tuple, on page dbPage, with locked cache handle
--dbPageHdl.
result: TupleHandle ←
DBStorageTuple.ConsTupleObject[tid: ConsTID[dbPage, slotIndex], cacheHint: dbPageHdl];
DBSegment.UnlockPage[dbPageHdl];
RETURN[result];
END;--Finish
END;
};--CreateSystemPageTuple


InitializeTuplePage: PROC[p: --writeable--LONG POINTER TO DBStorageVec.VecPage,
tsID: TID, pageTag: CARDINAL] = {
-- p points to a writeable cache page; this proc turns it into a page for storing tuples.
--It creates a tupleset dictionary with an entry for tsID, and makes the page's tag be pageTag.
--The page's allocLink is NullDBPage for Tuple pages, NotOnList for SystemTuple pages.
--The page's searchList links are set to NotOnList, independent of page type.

slotIndex: INTEGER; success: BOOLEAN;
tsDict: LONG POINTER TO DBStorageTSDict.TSDict;
DBStorageVec.InitializeVecPage[p, pageTag];
[slotIndex, success] ← DBStorageVec.AllocVec[p, DBStorageTSDict.SizeOfInitialTSDict];
IF DoChecks AND (~success OR slotIndex # DBStorageTSDict.TSDictSlotIndex) THEN
ERROR DBEnvironment.InternalError;
tsDict ← LOOPHOLE[DBStorageVec.VecOfSlot[p, DBStorageTSDict.TSDictSlotIndex]];
tsDict.allocLink ← SELECT pageTag FROM
DBStoragePagetags.Tuple => NullDBPage,
DBStoragePagetags.SystemTuple => NotOnList,
ENDCASE => ERROR;
tsDict.seq[1] ← [tuplesetID: tsID, next: NotOnList, prev: NotOnList];
DBStorageVec.SetTypeOfSlot[p, DBStorageTSDict.TSDictSlotIndex, DBStorageVectags.TSDictType];
};--InitializeTuplePage

TryAllocTuple: PROC[p: LONG POINTER TO DBStorageVec.VecPage, pValidHint: DBCache.CacheHandle,
tsID: TID, wordsForTupleFields: CARDINAL]
RETURNS[--slotIndex-- CARDINAL, --success-- BOOLEAN] = {
-- p points to a tuple page, and pValidHint is a valid cache hint for it; this proc attempts to
--create a new tuple with tuplesetID tsID and length wordsForTupleContents on this page.
--If this is possible without overflow, then slotIndex is the slot of the resulting tuple,
--and success is TRUE; otherwise slotIndex contains garbage and success is FALSE.
-- We compute (an upper bound for) the number of words needed, and proceed only if they are
--available. This means that we write the page only when necessary, and never have to undo
--partial updates. (We may fail when the room is really there, too).

slotIndex: CARDINAL;
entry: CARDINAL;
BEGIN--block for EXITS
BEGIN--block for tsDict variables
success: BOOLEAN;
tsIDFound: BOOLEAN;
[entry, tsIDFound] ← GetIndex[p, tsID];
IF ~tsIDFound AND entry > DBStorageVectags.MaxTuplesetPerPage THEN GOTO Failure;
IF (wordsForTupleFields + (DBStorageExtTuple.SizeOfNullTuple+SIZE[DBStorageVec.Slot]) +
(IF tsIDFound THEN 0 ELSE SIZE[DBStorageTSDict.TSDictEntry]))
> DBStorageVec.WordsLeftOnPage[p] THEN GOTO Failure;
-- Here we're sure to make it...
DBSegment.WriteLockedPage[pValidHint];
IF ~tsIDFound THEN {
[success] ← DBStorageVec.ModifyVec[p, DBStorageTSDict.TSDictSlotIndex,
SIZE[DBStorageTSDict.TSDictEntry], TRUE];
IF DoChecks AND ~success THEN ERROR DBEnvironment.InternalError;
LOOPHOLE[DBStorageVec.VecOfSlot[p, DBStorageTSDict.TSDictSlotIndex],
LONG POINTER TO DBStorageTSDict.TSDict].seq[entry] ←
[tuplesetID: tsID, next: NotOnList, prev: NotOnList];
};--IF
END;--block for tsDict variables
BEGIN--block for tuple variables
success: BOOLEAN;
tuplePtr: LONG POINTER TO DBStorageExtTuple.TupleBody;
[slotIndex, success] ←
DBStorageVec.AllocVec[p, wordsForTupleFields + DBStorageExtTuple.SizeOfNullTuple];
IF DoChecks AND ~success THEN ERROR DBEnvironment.InternalError;
tuplePtr ← LOOPHOLE[DBStorageVec.VecOfSlot[p, slotIndex]];
tuplePtr.groupOffset ← DBStorageExtTuple.SizeOfNullTuple + wordsForTupleFields;
IF wordsForTupleFields # 0 THEN BEGIN --zero out the tuple
tuplePtr.fields[0] ← 0;
PrincOpsUtils.LongCOPY[from: @tuplePtr.fields[0], nwords: wordsForTupleFields-1,
to: @tuplePtr.fields[1]];
END;--IF
DBStorageVec.SetTypeOfSlot[p, slotIndex, entry];
GOTO Success;
END;--block for tuple variables
EXITS
Success => RETURN[slotIndex, TRUE];
Failure => RETURN[UnlikelySlotIndex, FALSE];
END;
};--TryAllocTuple

GetIndex: PUBLIC PROC[p: LONG POINTER TO DBStorageVec.VecPage, tsID: TID]
RETURNS[CARDINAL, BOOLEAN] = {
-- Returns index of the TSDictEntry on page p that contains tsID as its tuplesetID field.
--Second result is TRUE iff entry was actually found; otherwise we return 1+number of entries in
--the TSDict, and FALSE.
-- Called from TryAllocTuple, GetEntry, NextScanTupleset.
tsDictPtr: LONG POINTER TO DBStorageTSDict.TSDict ← TSDictBaseFromPagePtr[p];
nEntries: CARDINAL ← NEntries[tsDictPtr];
FOR entry: CARDINAL IN [1..nEntries] DO
IF tsDictPtr.seq[entry].tuplesetID = tsID THEN GOTO FoundEntry;
REPEAT
FoundEntry => RETURN[entry, TRUE];
FINISHED => RETURN[nEntries+1, FALSE];
ENDLOOP;
};--IndexFromID


NEntries: PUBLIC PROC[tsDictPtr: LONG POINTER TO DBStorageTSDict.TSDict]
RETURNS[CARDINAL] = {
-- Returns the length (in elements, not words) of tsDictPtr.seq
-- Exported to DBStorageTSDict.
-- Called internally from GetIndex.
dictLen: CARDINAL ← tsDictPtr.header.length - DBStorageTSDict.SizeOfNullTSDict;
IF DoChecks AND (dictLen = 0 OR dictLen MOD SIZE[DBStorageTSDict.TSDictEntry] # 0) THEN
ERROR DBEnvironment.InternalError; -- BadTSDictLength
RETURN[dictLen/SIZE[DBStorageTSDict.TSDictEntry]];
};--NEntries


GetEntry: PUBLIC PROC[p: LONG POINTER TO DBStorageVec.VecPage, tsID: TID]
RETURNS[LONG POINTER TO DBStorageTSDict.TSDictEntry] = {
-- Returns ptr to the TSDictEntry on page p that contains tsID as its tuplesetID field.
--ERRORs InternalError if no such entry exists.
-- Called from CreateTuple.
tsDictPtr: LONG POINTER TO DBStorageTSDict.TSDict ← TSDictBaseFromPagePtr[p];
entry: CARDINAL; success: BOOLEAN;
[entry, success] ← GetIndex[p, tsID];
IF success THEN RETURN[@tsDictPtr.seq[entry]];
ERROR DBEnvironment.InternalError; -- TSDictEntryNotFound
};--GetEntry


TSDictBaseFromPagePtr: PROC[dbPagePtr: LONG POINTER TO DBStorageVec.VecPage]
RETURNS[LONG POINTER TO DBStorageTSDict.TSDict] = INLINE {
RETURN[LOOPHOLE[DBStorageVec.VecOfSlot[dbPagePtr, DBStorageTSDict.TSDictSlotIndex]]];
};--TSDictBaseFromPagePtr


EntryFromIndex: PUBLIC PROC[dbPagePtr: LONG POINTER TO DBStorageVec.VecPage, index: CARDINAL]
RETURNS[LONG POINTER TO DBStorageTSDict.TSDictEntry] = {
tsDict: LONG POINTER TO DBStorageTSDict.TSDict ← TSDictBaseFromPagePtr[dbPagePtr];
IF index > NEntries[tsDict] THEN ERROR InternalError; -- TSDictEntryNotFound
RETURN[@(tsDict.seq[index])];
};--EntryFromIndex

DestroyTuple: PUBLIC PROC[x: TupleHandle] = {
-- Destroys tuple x, and frees x's TupleObject.
-- x's group fields must have already been NILed out, since we don't even know where they are...

dbPage: DBPage;
slotIndex, localTSID: CARDINAL;
dbPagePtr: LONG POINTER TO DBStorageVec.VecPage; dbPageHdl: DBCache.CacheHandle;
pageEmpty: BOOLEAN ← FALSE;
xs: REF TupleObject[stored] = NARROW[x];
DBStats.Inc[StorageDestroyTuple];
[dbPage, slotIndex] ← DecomposeTID[xs.tid];
[dbPageHdl, dbPagePtr] ← DBSegment.WritePage[dbPage, xs.cacheHint];
DBStoragePagetags.AssertPageIsAnyTuplePage[dbPagePtr];
localTSID ← DBStorageVec.TypeOfSlot[dbPagePtr, slotIndex];
AssertTupleTypeIsOK[dbPagePtr, localTSID];
-- To here, we've just been gathering useful information about the tuple we're destroying.
DBStorageTuple.InvalidateMatchingTuples[x]; -- invalidate all x-like tuple objects
DBStorageVec.FreeVec[dbPagePtr, slotIndex]; -- free tuple storage on page
-- With overflow pages and long strings, things are not quite so simple. You must first
--deallocate all vecs that depend on this one. Such vecs might live on other pages, which
--might become empty, ...
FOR i: CARDINAL IN [1 .. DBStorageVec.HighSlotIndexOfPage[dbPagePtr]] DO
IF DBStorageVec.TypeOfSlot[dbPagePtr, i] = localTSID THEN GOTO FoundTupleFromSameTS;
REPEAT
FoundTupleFromSameTS => {}; --No changes to TSDict are necessary
FINISHED => {
RemoveTSDictEntry[dbPage, dbPagePtr, localTSID];
pageEmpty ← (TSDictBaseFromPagePtr[dbPagePtr].header.length = DBStorageTSDict.SizeOfNullTSDict) };
--Deleted page's last tuple from TS; this does not work with colocation of user tuples.
ENDLOOP;
IF pageEmpty THEN {
-- Page is now empty. Its TSDict must be empty, hence there are no pointers to this page,
--and its lock count is 1. Free it back to its segment. (Someday maybe to TS page allocator).
DBSegment.FreePage[DBSegment.SegmentIDFromDBPage[dbPage], dbPage, dbPageHdl]; }
ELSE {
IF DBStorageVec.TagOfPage[dbPagePtr] = DBStoragePagetags.Tuple THEN {
-- User tuple page is not empty, but it may have just gone below the 'full' threshold.
--If so, then it should be placed on an alloc list if it is not already on one.
MaybePutOnAllocList[dbPage, dbPagePtr, dbPageHdl] };
DBSegment.UnlockPage[dbPageHdl];
};--IF
--DBStorageTuple.FreeTupleObject[x];
};--DestroyTuple


AssertTupleTypeIsOK: PROC[p: LONG POINTER TO DBStorageVec.VecPage, type: CARDINAL] = INLINE {
-- This test should be made stronger by having it examine the TSDict itself.
SELECT type FROM
IN [1..DBStorageVectags.MaxTuplesetPerPage] => {};
ENDCASE => ERROR InternalError; -- BadLocalTSID
};--AssertTupleTypeIsOK

RemoveTSDictEntry: PROC[dbPage: DBPage, dbPagePtr: LONG POINTER TO DBStorageVec.VecPage,
entry: CARDINAL] = {
-- dbPage is a (system or user) tuple page, writeable-locked in cache, and dbPagePtr points to it.
--This proc deletes the entry-th entry in the TSDict on this page, and makes the
--rest of the world consistent with this.
isUserPage: BOOLEAN ← SELECT DBStorageVec.TagOfPage[dbPagePtr] FROM
DBStoragePagetags.Tuple => TRUE,
DBStoragePagetags.SystemTuple => FALSE,
ENDCASE => ERROR;
tsDict: LONG POINTER TO DBStorageTSDict.TSDict ← TSDictBaseFromPagePtr[dbPagePtr];
tsID: TID; prevDBPage, nextDBPage: DBPage;
IF isUserPage THEN {
ts: TupleHandle;
-- Extract the next and prev pages, and the TSID, from the entry we're deleting
[tuplesetID: tsID, prev: prevDBPage, next: nextDBPage] ← tsDict.seq[entry];
ts ← DBStorageTuple.ConsTupleObject[tsID];
-- If page is on ts's allocList, remove it (it doesn't contain any tuples from ts).
--(Page might also be empty now, in which case alloc list ptr to it would soon "dangle").
IF entry=1 AND tsDict.allocLink#NotOnList THEN {
BypassPageOnAllocList[ts, dbPage, tsDict.allocLink];
tsDict.allocLink ← NotOnList;
};--IF
-- Delete the page from the doubly-linked list of pages for this tupleset.
WriteTSDictField[prevDBPage, ts, next, nextDBPage];
WriteTSDictField[nextDBPage, ts, prev, prevDBPage];
--DBStorageTuple.FreeTupleObject[ts];
};--IF isUserPage
-- Move higher entries down and shrink the vec by 1 entry (may now even have 0 entries)
FOR i: CARDINAL IN [entry .. NEntries[tsDict]) DO
tsDict.seq[i] ← tsDict.seq[i+1];
ENDLOOP;
[] ← DBStorageVec.ModifyVec[dbPagePtr, DBStorageTSDict.TSDictSlotIndex,
-SIZE[DBStorageTSDict.TSDictEntry], TRUE];
-- For each tuple vec on page whose local tsid is > entry, reduce by 1.
FOR i: CARDINAL IN [1 .. DBStorageVec.HighSlotIndexOfPage[dbPagePtr]] DO
type: CARDINAL ← DBStorageVec.TypeOfSlot[dbPagePtr, i];
IF type IN [1..DBStorageVectags.MaxTuplesetPerPage] AND type > entry THEN
DBStorageVec.SetTypeOfSlot[dbPagePtr, i, type-1];
ENDLOOP;
IF isUserPage THEN {
-- Update active tupleset scans to account for changes to this page.
DBStorageTuplesetScan.NoticeDeletion[dbPage, tsID, entry, nextDBPage];
};--IF isUserPage
};--RemoveTSDictEntry

TSPart: TYPE = {next, prev};

WriteTSDictField: PROC[page: DBPage, ts: TupleHandle, part: TSPart, val: DBPage] = {
-- If page=NullDBPage, then this writes the specified part of the tupleset object;
--otherwise it writes part of the ts dict entry on the given page for the given tupleset.
cacheHint: DBCache.CacheHandle;
IF page = NullDBPage THEN {
tsObjPtr: LONG POINTER TO DBStorageTupleset.TuplesetObject;
[tsObjPtr, cacheHint] ← DBStoragePrivate.GetNWordBase[ts, DBStorageField.TuplesetFieldHandle[]];
DBSegment.WriteLockedPage[cacheHint];
SELECT part FROM
next => tsObjPtr.searchList.next ← val;
prev => tsObjPtr.searchList.prev ← val;
ENDCASE => ERROR; }
ELSE {
pagePtr: LONG POINTER TO DBStorageVec.VecPage;
tsDictEntry: LONG POINTER TO DBStorageTSDict.TSDictEntry;
[cacheHint, pagePtr] ← DBSegment.WritePage[page, NIL];
DBStoragePagetags.AssertPageIsTuplePage[pagePtr];
tsDictEntry ← GetEntry[pagePtr, ts.tid];
SELECT part FROM
next => tsDictEntry.next ← val;
prev => tsDictEntry.prev ← val;
ENDCASE => ERROR;
};--IF
DBSegment.UnlockPage[cacheHint];
};--WriteTSDictField

BypassPageOnAllocList: PROC[ts: TupleHandle, dbPageToRemove, dbPageReplacement: DBPage] = {
-- Make the link of ts's allocList that points to dbPageToRemove point instead to
--dbPageReplacement.
-- Called from: RemoveTSDictEntry.
-- This looks pretty gross. A problem with secondary-storage data structures is that we can't
--put in header nodes to make things be uniform to the program; the header node would in this
--case burn up a page! So there is invariably a special case involving the list head.
-- Another clumsy thing is the number of distinct variables that I require, and the number
--of distinct operations necessary, to maintain and follow a page pointer. This makes me
--want to minimize the number of these; hence this proc never looks at more than one page at
--a time, and the caller is required to do some of the work.
tsObjPtr: LONG POINTER TO DBStorageTupleset.TuplesetObject;
cacheHdl: DBCache.CacheHandle;
[tsObjPtr, cacheHdl] ← DBStoragePrivate.GetNWordBase[ts, DBStorageField.TuplesetFieldHandle[]];
IF tsObjPtr.allocList = dbPageToRemove THEN {
DBSegment.WriteLockedPage[cacheHdl]; tsObjPtr.allocList ← dbPageReplacement; }
ELSE {
curPage: DBPage ← tsObjPtr.allocList;
curPagePtr: LONG POINTER TO DBStorageVec.VecPage;
tsDict: LONG POINTER TO DBStorageTSDict.TSDict;
DO
DBSegment.UnlockPage[cacheHdl]; --unlock page containing the pointer curPage
IF curPage = NullDBPage OR curPage = NotOnList THEN InternalError;
--ran off end of list without finding dbPageToRemove
[cacheHdl, curPagePtr] ← DBSegment.ReadPage[curPage, NIL];
DBStoragePagetags.AssertPageIsTuplePage[curPagePtr];
tsDict ← TSDictBaseFromPagePtr[curPagePtr];
IF tsDict.allocLink = dbPageToRemove THEN {
DBSegment.WriteLockedPage[cacheHdl]; tsDict.allocLink ← dbPageReplacement;
EXIT; }
ELSE curPage ← tsDict.allocLink;
ENDLOOP
};--IF
DBSegment.UnlockPage[cacheHdl]; --unlock the page we modified
};--BypassPageOnAllocList


MaybePutOnAllocList: PROC[dbPage: DBPage, dbPagePtr: LONG POINTER TO DBStorageVec.VecPage,
dbPageHdl: DBCache.CacheHandle] = {
-- if page pointed to by dbPagePtr is not on an allocList and under threshold, put it on allocList
--do not change lock count of dbPageHdl.
tsDict: LONG POINTER TO DBStorageTSDict.TSDict ← TSDictBaseFromPagePtr[dbPagePtr];
IF tsDict.allocLink = NotOnList AND
DBStorageVec.WordsLeftOnPage[dbPagePtr] >= AllocThreshold THEN {
tsObjPtr: LONG POINTER TO DBStorageTupleset.TuplesetObject;
tsObjHdl: DBCache.CacheHandle;
{ -- make tupleset object addressible
ts: TupleHandle;
IF tsDict.header.length = DBStorageTSDict.SizeOfNullTSDict THEN
ERROR DBEnvironment.InternalError; --we expected the thing to be nonempty!
ts ← DBStorageTuple.ConsTupleObject[tsDict.seq[1].tuplesetID];
[tsObjPtr,tsObjHdl] ← DBStoragePrivate.GetNWordBase[ts, DBStorageField.TuplesetFieldHandle[]];
--DBStorageTuple.FreeTupleObject[ts];
};
DBSegment.WriteLockedPage[dbPageHdl]; tsDict.allocLink ← tsObjPtr.allocList;
DBSegment.WriteLockedPage[tsObjHdl]; tsObjPtr.allocList ← dbPage;
DBSegment.UnlockPage[tsObjHdl];
};--IF
};--MaybePutOnAllocList


FreeTupleHandle: PUBLIC PROC[x: TupleHandle] = {
-- Deallocates the storage used by the TupleObject pointed to by x. The caller is
--responsible not to keep dangling references to this object.
--DBStorageTuple.FreeTupleObject[x];
};--FreeTupleHandle


END.--StorageImplC

-- Module History

Created by MBrown on February 17, 1980 2:13 PM

Changed by MBrown on February 25, 1980 5:18 PM
-- All coded but Create[User]Tuple, DestroyTuple; testing can proceed.

Changed by MBrown on 28-Feb-80 22:12
-- Make NEntriesInTSDict PUBLIC, for use in StorageImplB.

Changed by MBrown on April 9, 1980 9:24 AM
-- When y#NIL in CreateSystemPageTuple, ignore s and place tuple in same segment as y.

Changed by MBrown on April 9, 1980 3:43 PM
-- Coded CreateTuple, with limitations (1) no colocation, (2) no delete tuple. Added check of
--page tag to CreateSystemPageTuple.

Changed by MBrown on April 9, 1980 6:14 PM
-- Coded tupleset scan stuff. Cleaned up the TSDict searching stuff; that loop appears in
--only one place now. Procedure calls are not to be avoided for now.

Changed by MBrown on April 10, 1980 10:15 AM
-- In the process of cleaning up the TSDict stuff I introduced a bug in TryAllocTuple:
--entry was declared twice, which prevented communication between the two sections.

Changed by MBrown on 11-Apr-80 11:07
-- In CreateTuple, in the case that the tuple fits on the first page of searchList,
--dbPage was not being assigned any value. Next time, make sure that the code before
--a GOTO sets up the destination's invariants properly!

Changed by MBrown on April 11, 1980 11:19 AM
-- Changed CreateThreshold from 3/4 of pagesize to 1/4. (This is what I intended all
--along, but the code didn't agree).

Changed by MBrown on April 11, 1980 11:54 AM
-- Tupleset scan returned the same tuple over and over; changed FOR slotI IN [scan.slotIndex ..
--to FOR slotI IN [scan.slotIndex + 1 ..

Changed by MBrown on April 17, 1980 11:30 PM
-- Added USING clauses.

Changed by MBrown on April 21, 1980 10:09 PM
-- In TryAllocTuple, when extending the TSDict, I computed the pointer to the vec BEFORE
--extending the vec. But extending the vec is almost guaranteed to move it...

Changed by MBrown on April 23, 1980 9:16 PM
-- Made FreeTupleHandle check for x#NIL before calling DeallocTupleHandle.

Changed by MBrown on June 8, 1980 8:46 PM
-- Changed DBStoragePrivateA.TuplesetFieldHandle to DBStorageField.TuplesetFieldHandle.

Changed by MBrown on June 17, 1980 8:48 AM
-- Moved tupleset scan stuff to StorageTuplesetScanImpl, and made TSDict procs public via
--DBStorageTSDict.

Changed by MBrown on June 24, 1980 11:23 AM
-- Made SystemTuplesetObject an opaque type.

Changed by MBrown on July 24, 1980 10:05 PM
-- Implemented DestroyTuple. This involved a near-total rewrite of CreateTuple. Alloc list
--scheme is now in, and we both insert and delete from search lists. The number of modules
--affected was 14.

Changed by MBrown on July 31, 1980 3:39 PM
-- Changed PageFullP to use expectedNGroups information from tupleset object. Added SystemPageFullP.

Changed by MBrown on August 1, 1980 1:29 AM
-- Bug in PageFullP: I had computed the difference of two cardinals, forgetting that the
--difference might be negative!

Changed by MBrown on August 25, 1980 1:45 PM
-- Modified PageFullP to account for space used by LStrings. Var fields can still cause page
--overflow, but only if they start out smaller than the lengthHint and then get larger.

Changed by MBrown on August 29, 1980 10:58 AM
-- In PageFullP, if tuple has longer group list than expected, then use the actual rather than
-the expected length.

Changed by MBrown on September 22, 1980 5:14 PM
-- In MaybePutOnAllocList, can't call NEntries since that raises a signal. In DestroyTuple,
--keep explicit flag that page is empty of tuples, to avoid problem with LStrings that aren't
--going away.

Changed by MBrown on September 23, 1980 3:27 PM
-- In DestroyTuple, was trying to put system tuple on alloc list.

Changed by MBrown on September 26, 1980 3:57 PM
-- Converted to use new DBException.

Changed by Cattell on October 1, 1980 9:52 AM [10]
-- Added DBStorageVec.FreeType to SELECT on slotType in PageFullP: was generating error.

Changed by MBrown on December 7, 1980 12:25 PM
-- Added DBStats calls for CreateTuple, CreateSystemPageTuple, DestroyTuple.

Changed by MBrown on February 27, 1981 5:08 PM
-- Pre-Pilot changes.

Changed by MBrown on March 12, 1981 9:57 AM
-- Bug in DestroyTuple: decided page was empty when last system tuple of a particular type
--was deleted. Still does not handle colocation of user tuples.
-- Pre-Pilot changes.

Changed by MBrown on 19-Jun-81 13:46:51
-- Conversion to new TupleHandle representation.

Changed by MBrown on 7-Aug-81 15:35:37
-- In DestroyTuple, if pageEmpty then call to NEntries raised signal. Fix is same as for
--the September 22, 1980 5:14 PM bug in MaybePutOnAllocList.