WalnutDBMsgSetsImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Willie-Sue, September 17, 1986 11:30:26 am PDT
Donahue, May 12, 1986 2:13:42 pm PDT
Contents: types and procedures dealing with the Walnut message database
Initiated by Willie-Sue, September 24, 1984
DIRECTORY
Ascii USING [Lower],
Atom USING [MakeAtomFromRefText, GetPName],
BasicTime USING [GMT, nullGMT],
DB USING [Aborted, Error, Failure,
CreateRelship, DeclareEntity, DestroyEntity, DestroyRelship, DomainSubset,
EntityInfo, EntityEq, FirstRelship, GetF, LookupEntity, LookupProperty, NextEntity,
NextRelship, NullEntity, NullRelship, PrevRelship,
RelationSubset, ReleaseEntitySet, ReleaseRelshipSet, RelshipsWithEntityField, SetF,
L2C, L2VS, B2V, E2V, I2V, V2E, V2I, V2S,
Constraint, Entity, EntitySet, Relship, RelshipSet, Value, ValueSequence],
IO,
RefTab USING [Ref, Val, Create, Delete, Fetch, Store],
RefText USING [line, InlineAppendChar],
Rope,
WalnutDefs USING [dontCareDomainVersion, dontCareMsgSetVersion, Error, MsgSet, VersionMismatch],
WalnutDB,
WalnutRegistry USING [MsgGroup, MsgGroupSize],
WalnutRegistryPrivate USING [ CheckForMsgGroupRegistration,
NotifyForEvent, NotifyForMove, NotifyForMsgEvent, NotifyForMsgGroup],
WalnutRoot USING [CommitAndContinue],
WalnutSchema;
WalnutDBMsgSetsImpl: CEDAR PROGRAM
IMPORTS
Ascii, Atom, DB, IO, RefTab, RefText, Rope,
WalnutDefs, WalnutDB,
WalnutRegistryPrivate,
WalnutRoot, WalnutSchema
EXPORTS WalnutDB =
BEGIN OPEN WalnutSchema;
Types
ROPE: TYPE = Rope.ROPE;
Entity: TYPE = DB.Entity;
EntitySet: TYPE = DB.EntitySet;
Relship: TYPE = DB.Relship;
RelshipSet: TYPE = DB.RelshipSet;
MsgSet: TYPE = WalnutDefs.MsgSet;
dontCareDomainVersion: INT = WalnutDefs.dontCareDomainVersion;
dontCareMsgSetVersion: INT = WalnutDefs.dontCareMsgSetVersion;
CheckReportProc: TYPE = WalnutDB.CheckReportProc;
Internal Types and Variables
Value: TYPE = DB.Value;
DBMinusOneInt: Value ← DB.I2V[-1];
DBZeroInt: Value ← DB.I2V[0];
DBTrueBool: Value ← DB.B2V[TRUE];
DBFalseBool: Value ← DB.B2V[FALSE];
nullValue: Value = [null[]];
nullDate: Value = [time[BasicTime.nullGMT]];
ActiveMsgSetName: ROPE = "Active";
DeletedMsgSetName: ROPE = "Deleted";
IsEntity: TYPE = RECORD [entity: Entity, exists: BOOL];
MSInfo's are stored in the msgSetsTable
MSInfo: TYPE = REF MSInfoObject;
MSInfoObject: TYPE = RECORD [canonicalName: ROPE, entity: Entity, versionRel: Relship];
nameText: REF TEXT = NEW[TEXT[RefText.line]];
LazyEnumerator: TYPE = REF LazyEnumeratorRec;
LazyEnumeratorRec: PUBLIC TYPE = RECORD[ msgSet: MsgSet, pos: INT, set: DB.RelshipSet, checkShow: BOOL];
Operations on Message Sets
MsgSetExists: PUBLIC PROC[name: ROPE, msDomainVersion: INT] RETURNS[existed: BOOL, msVersion: INT] = {
Does this message set already exist in the database.
IsMsgSet: PROC = {
msI: MSInfo;
CheckDomainVersion[msDomainVersion];
[msI, msVersion] ← GetMsgSetAndVersion[name: name];
existed ← msI # NIL;
};
WalnutDB.CarefullyApply[IsMsgSet];
};
CreateMsgSet: PUBLIC PROC[name: ROPE, msDomainVersion: INT] RETURNS [existed: BOOL, msVersion: INT] = {
Create this message set if it doesn't already exist in the database.
CrMsgSet: PROC = {
msI: MSInfo;
mse: Entity;
cName: ROPE;
aName: ATOM;
CheckDomainVersion[msDomainVersion];
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF existed ← (msI # NIL) THEN RETURN;
cName ← Atom.GetPName[aName ← CanonicalName[name]];
mse ← DB.DeclareEntity[MsgSetDomain, cName, TRUE]; -- newOnly
msI ← NEW[MSInfoObject ← [canonicalName: cName, entity: mse, versionRel: NIL] ];
msI.versionRel ← DB.CreateRelship[msBasicInfo,
DB.L2VS[LIST [ DB.E2V[mse], [integer[0]], [integer[1]], [rope[name]]] ] ];
ChangeGlobalMsgSetInfo[1];
[] ← RefTab.Store[WalnutDB.msgSetsTable, aName, msI];
};
WalnutDB.CarefullyApply[CrMsgSet];
};
NumInMsgSet: PUBLIC PROC[name: ROPE] RETURNS[num: INT, msVersion: INT] = {
NumIn: PROC = {
msI: MSInfo;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN num← 0
ELSE num ← DB.V2I[DB.GetF[msI.versionRel, msBICount]];
};
WalnutDB.CarefullyApply[NumIn];
};
EmptyMsgSet: PUBLIC PROC[msgSet: MsgSet, report: CheckReportProc]
RETURNS[someInDeleted: BOOL] = {
Removes any messages from msgSet - long running op
EmptyIt: PROC = {
numIn: INT;
msI: MSInfo = CheckMsgSetEntity[msgSet];
IF msI = NIL THEN RETURN;
numIn ← DB.V2I[DB.GetF[msI.versionRel, msBICount]];
someInDeleted ← EmptyThisMsgSet[msI.entity, msgSet.name, report];
IF numIn # 0 THEN WalnutDB.ChangeCountInMsgSet[msI.entity, -numIn];
};
IF WalnutDB.EqMsgSets[msgSet.name, WalnutDB.deletedMsgSet.name] THEN {
WalnutDB.SetOpInProgressPos[-1];
ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't empty the Deleted MsgSet"];
};
WalnutDB.CarefullyApply[EmptyIt];
};
DestroyMsgSet: PUBLIC PROC[msgSet: MsgSet, msDomainVersion: INT, report: CheckReportProc]
RETURNS[someInDeleted: BOOL] = {
Destroys the given msgSet, removing any messages from it first (uses equivalent of RemoveMsgFromMsgSet below). If the message set is "Deleted" then WalnutDefs.IError[$db, $InvalidMsgSet] - long running op
DestroyIt: PROC = {
msI: MSInfo;
CheckDomainVersion[msDomainVersion];
msI ← CheckMsgSetEntity[msgSet];
IF msI = NIL THEN RETURN;
someInDeleted ← EmptyThisMsgSet[msI.entity, msgSet.name, report];
DB.DestroyEntity[msI.entity];
ChangeGlobalMsgSetInfo[-1];
};
IF WalnutDB.EqMsgSets[msgSet.name, WalnutDB.deletedMsgSet.name] THEN {
WalnutDB.SetOpInProgressPos[-1];
ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't destroy the Deleted MsgSet"];
};
WalnutDB.CarefullyApply[DestroyIt];
};
VerifyMsgSet: PUBLIC PROC[msgSet: MsgSet] RETURNS[exists: BOOL] = {
Vms: PROC = {
msI: MSInfo ← CheckMsgSetEntity[msgSet];
exists ← msI # NIL;
};
WalnutDB.CarefullyApply[Vms];
};
VerifyDomainVersion: PUBLIC PROC[msDomainVersion: INT] = {
Vdv: PROC = { CheckDomainVersion[msDomainVersion] };
WalnutDB.CarefullyApply[Vdv];
};
ExpungeMsgs: PUBLIC PROC[deletedVersion: INT, report: CheckReportProc] = {
Destroys the Deleted message set - long running op
OPEN WalnutDB;
DestroyDeletedMsgs: PROC = {
msI: MSInfo = CheckMsgSetEntity[[DeletedMsgSetName, deletedVersion]];
BEGIN
deletedCount: INT = DB.V2I[DB.GetF[msI.versionRel, msBICount]];
rs: RelshipSet = DB.RelshipsWithEntityField[cdRelation, cdMsgSet, deletedMessageSet];
sinceLastCommit: INT ← 0;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
delArray: WalnutRegistry.MsgGroup;
numDel: CARDINAL ← 0;
count: INT ← 0;
doMsgGroup: BOOL ← WalnutRegistryPrivate.CheckForMsgGroupRegistration[];
IF doMsgGroup THEN delArray ← ALL[NIL];
If the counts are wrong, change them. They may already have been changed if you're restarting an Expunge
IF deletedCount # 0 THEN {
ChangeCountInMsgSet[deletedMessageSet, -deletedCount];
ChangeCountOfMsgs[-deletedCount];
This counts as one operation
sinceLastCommit ← sinceLastCommit + 1
};
BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]};
rel: Relship;
rLogInfo: Relship = DB.FirstRelship[gLogInfo];
bytesDestroyed: INTDB.V2I[DB.GetF[rLogInfo, gBytesInDestroyedMsgs]];
firstDestroyedPos: INTDB.V2I[DB.GetF[rLogInfo, gFirstDestroyedMsgPos]];
UNTIL (rel← DB.NextRelship[rs]) = NIL DO
me: Entity = DB.V2E[DB.GetF[rel, cdMsg]];
textRel: Relship = DB.LookupProperty[mTextInfo, me];
startPos: INTDB.V2I[DB.GetF[textRel, mTIEntryStart]];
thisLen: INTDB.V2I[DB.GetF[textRel, mTITextOffset]] +
DB.V2I[DB.GetF[textRel, mTITextLen]] + DB.V2I[DB.GetF[textRel, mTIFormatLen]];
bytesDestroyed ← bytesDestroyed + thisLen;
IF firstDestroyedPos = 0 OR startPos < firstDestroyedPos THEN
firstDestroyedPos ← startPos;
IF doMsgGroup THEN {
delArray[numDel] ← DB.EntityInfo[me].name;
numDel ← numDel + 1;
};
DB.DestroyEntity[me];
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency THEN {
DB.SetF[rLogInfo, gBytesInDestroyedMsgs, DB.I2V[bytesDestroyed]];
DB.SetF[rLogInfo, gFirstDestroyedMsgPos, DB.I2V[firstDestroyedPos]];
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
IF doMsgGroup THEN {
WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray];
delArray ← ALL[NIL];  -- clear out the Array
numDel ← 0;
};
};
IF report # NIL THEN count ← CheckCount[count, report];
ENDLOOP;
DB.ReleaseRelshipSet[rs];
IF sinceLastCommit # 0 THEN {
DB.SetF[rLogInfo, gBytesInDestroyedMsgs, DB.I2V[bytesDestroyed]];
DB.SetF[rLogInfo, gFirstDestroyedMsgPos, DB.I2V[firstDestroyedPos]];
};
BEGIN  -- change the version number of Deleted
delRel: Relship = DB.LookupProperty[msBasicInfo, deletedMessageSet];
DB.SetF[delRel, msBIVersion, DB.I2V[DB.V2I[DB.GetF[delRel, msBIVersion]] + 1]];
WalnutRoot.CommitAndContinue[];
IF doMsgGroup AND (numDel # 0) THEN
WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray];
WalnutRegistryPrivate.NotifyForEvent[expungeComplete];
END;
END;
END;
};
[]← CarefullyApply[DestroyDeletedMsgs]
};
MsgSetsInfo: PUBLIC PROC RETURNS[version, num: INT] = {
Msv: PROC = {
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
version ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
num ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
};
WalnutDB.CarefullyApply[Msv];
};
Operations to Move Messages Among Message Sets
AddMsg: PUBLIC PROC[msg: ROPE, from, to: MsgSet] RETURNS[exists: BOOL] = {
Adds Msg to MsgSet, if it's not already in it. IF msgSet=deletedMsgSet, does nothing and returns exists=FALSE
Amt: PROC = {
m: IsEntity ← GetMsgEntity[msg];
msI: MSInfo;
IF ~m.exists THEN RETURN;
[] ← CheckMsgSetEntity[from];
msI ← CheckMsgSetEntity[to];
IF msI = NIL THEN RETURN;  -- can't create msgset here
{ rs: RelshipSet = DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity];
wasInDeleted: BOOLFALSE;
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs];
DO
rel: Relship = DB.NextRelship[rs];
IF DB.NullRelship[rel] THEN EXIT;
BEGIN
msgSet: DB.Entity = DB.V2E[DB.GetF[rel, cdMsgSet]];
IF DB.EntityEq[msI.entity, msgSet] THEN { exists ← TRUE; EXIT };
IF DB.EntityEq[msgSet, WalnutDB.deletedMessageSet] THEN {
DB.DestroyRelship[rel];
WalnutDB.ChangeCountInMsgSet[WalnutDB.deletedMessageSet, -1] }
END
ENDLOOP;
DB.ReleaseRelshipSet[rs]
END;
};
IF NOT exists THEN AddMsgTo[m.entity, msI.entity];
};
exists ← FALSE;
IF WalnutDB.EqMsgSets[to.name, WalnutDB.deletedMsgSet.name] THEN RETURN;
WalnutDB.CarefullyApply[Amt];
};
RemoveMsg: PUBLIC PROC[msg: ROPE, from: MsgSet, deletedVersion: INT] RETURNS[deleted: BOOL] = {
IF removing msg from msgSet would leave it in no MsgSet, then msg gets added to the distinguished MsgSet Deleted, and returns deleted = TRUE
Rmf: PROC = {
m: IsEntity;
msI: MSInfo;
date: Value ← nullDate;
rs: RelshipSet;
thisCDRel: Relship;
IF NOT (m← GetMsgEntity[msg]).exists THEN RETURN;
IF (msI← CheckMsgSetEntity[from]) = NIL THEN RETURN;
TRUSTED {date ← DB.GetF[DB.LookupProperty[mInfo, m.entity], mDateIs] };
deleted ← TRUE; -- We're committed to deleting the message unless we find it in another message set
rs ← DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity];
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs];
DO
rel: Relship = DB.NextRelship[rs];
IF DB.NullRelship[rel] THEN EXIT;
IF DB.EntityEq[msI.entity, DB.V2E[DB.GetF[rel, cdMsgSet]]] THEN thisCDRel ← rel
ELSE deleted ← FALSE
ENDLOOP;
DB.ReleaseRelshipSet[rs];
END;
IF thisCDRel = NIL THEN { deleted ← FALSE; RETURN }; -- Something strange here
WalnutDB.ChangeCountInMsgSet[msI.entity, -1];
IF NOT deleted THEN DB.DestroyRelship[thisCDRel]
ELSE {
DB.SetF[thisCDRel, cdMsgSet, DB.E2V[WalnutDB.deletedMessageSet]];
WalnutDB.ChangeCountInMsgSet[WalnutDB.deletedMessageSet, 1] }
};
deleted← FALSE;
WalnutDB.CarefullyApply[Rmf];
};
MoveMsg: PUBLIC PROC[msg: ROPE, from: MsgSet, to: MsgSet] RETURNS [exists: BOOL] = {
Move the message. Note that the result of a move may be that a message becomes deleted (if to was the Deleted message set) or undeleted (if from is the Deleted message set)
DoMove: PROC = {
m: IsEntity;
fromMsI, toMsI: MSInfo;
date: Value ← nullDate;
rs: RelshipSet;
fromCDRelship, toCDRelship: Relship;
IF NOT (m ← GetMsgEntity[msg]).exists THEN RETURN;
IF (fromMsI ← CheckMsgSetEntity[from]) = NIL THEN RETURN;
IF (toMsI ← CheckMsgSetEntity[to]) = NIL THEN RETURN;
TRUSTED {date ← DB.GetF[DB.LookupProperty[mInfo, m.entity], mDateIs] };
rs ← DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity];
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs];
DO
rel: Relship = DB.NextRelship[rs];
IF DB.NullRelship[rel] THEN EXIT;
IF DB.EntityEq[fromMsI.entity, DB.V2E[DB.GetF[rel, cdMsgSet]]] THEN
fromCDRelship ← rel
ELSE IF DB.EntityEq[toMsI.entity, DB.V2E[DB.GetF[rel, cdMsgSet]]] THEN
toCDRelship ← rel;
ENDLOOP;
DB.ReleaseRelshipSet[rs];
END;
IF fromCDRelship = NIL THEN {exists ← FALSE; RETURN};
exists ← toCDRelship # NIL;
IF exists THEN DB.DestroyRelship[fromCDRelship]
ELSE DB.SetF[fromCDRelship, cdMsgSet, DB.E2V[toMsI.entity]];
WalnutDB.ChangeCountInMsgSet[fromMsI.entity, -1];
IF NOT exists THEN WalnutDB.ChangeCountInMsgSet[toMsI.entity, 1];
};
IF WalnutDB.EqMsgSets[from.name, to.name] THEN RETURN[TRUE]; -- don't do anything
WalnutDB.CarefullyApply[DoMove];
};
Enumerations
MsgsEnumeration: PUBLIC PROC [alphaOrder: BOOL] RETURNS [mL: LIST OF ROPE] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum: PROC = {
last: LIST OF ROPE;
enum: EntitySet ←
IF alphaOrder THEN
DB.DomainSubset[MsgDomain, "\000", "\177", First] ELSE
DB.DomainSubset[MsgDomain, NIL, NIL, First];
mL ← NIL;
BEGIN ENABLE UNWIND => IF enum#NIL THEN GOTO end;
e: Entity;
msg: ROPE;
FOR e ← DB.NextEntity[enum], DB.NextEntity[enum] UNTIL e = NIL DO
msg ← DB.EntityInfo[e].name;
IF mL = NIL THEN mL ← last ← CONS[msg, NIL] ELSE
{ last.rest ← CONS[msg, NIL]; last ← last.rest};
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDB.CarefullyApply[MEnum];
IF ok THEN RETURN
ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During Msgs enumeration"];
EXITS
stop => WalnutDefs.Error[$db, $TransactionAbort, "During Msgs enumeration"];
END };
MsgsInSetEnumeration: PUBLIC PROC[name: ROPE, fromStart: BOOL] RETURNS [mL: LIST OF ROPE, msVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum: PROC = {
msI: MSInfo;
enum: RelshipSet;
checkShow: BOOL;
constraint: DB.Constraint;
lastInList: LIST OF ROPENIL;
mL ← NIL;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN {ok ← TRUE; RETURN};
checkShow ← name.Equal["Active", FALSE];
constraint ← DB.L2C[LIST[[entity[msI.entity]], [time[]]]];
enum ←
DB.RelationSubset[cdRelation, cdIndex, constraint, IF fromStart THEN First ELSE Last];
BEGIN ENABLE UNWIND => GOTO end;
DO
rel: Relship = DB.NextRelship[enum];
me: Entity;
IF rel = NIL THEN EXIT;
me ← DB.V2E[DB.GetF[rel, cdMsg]];
IF checkShow THEN {
sRel: Relship = DB.LookupProperty[mInfo, me];
IF NOT DB.NullEntity[DB.V2E[DB.GetF[sRel, mShowIs]]] THEN LOOP; -- mShowIs is unaccepted
};
IF fromStart THEN {
thisL: LIST OF ROPE = CONS[DB.EntityInfo[me].name, NIL];
IF mL = NIL THEN mL ← lastInList ← thisL
ELSE {
lastInList.rest ← thisL;
lastInList ← lastInList.rest;
};
}
ELSE mL ← CONS[DB.EntityInfo[me].name, mL];
ENDLOOP;
ok ← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDB.CarefullyApply[MEnum];
IF ok THEN RETURN
ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
EXITS
stop => WalnutDefs.Error[$db, $TransactionAbort, "During MsgSets enumeration"];
END };
MsgSetsNames: PUBLIC PROC[alphaOrder: BOOL] RETURNS[msL: LIST OF ROPE, msDomainVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MSEnum: PROC = {
last: LIST OF ROPE;
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
enum: EntitySet ←
IF alphaOrder THEN
DB.DomainSubset[MsgSetDomain, "\000", "\177", First] ELSE
DB.DomainSubset[MsgSetDomain, NIL, NIL, First];
msDomainVersion ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
msL ← NIL;
BEGIN ENABLE UNWIND => IF enum#NIL THEN GOTO end;
e: Entity;
FOR e ← DB.NextEntity[enum], DB.NextEntity[enum] UNTIL e = NIL DO
thisName: ROPE =
DB.V2S[DB.GetF[DB.LookupProperty[msBasicInfo, e], msPrintNameIs]];
IF msL = NIL THEN msL ← last ← CONS[thisName, NIL] ELSE
{ last.rest ← CONS[thisName, NIL]; last ← last.rest};
ENDLOOP;
ok ← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDB.CarefullyApply[MSEnum];
IF ok THEN RETURN
ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
EXITS
stop => WalnutDefs.Error[$db, $TransactionAbort, "During MsgSets enumeration"];
END };
EnumerateMsgSets: PUBLIC PROC[alphaOrder: BOOL, proc: PROC[msgSet: MsgSet]]
RETURNS[msDomainVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MSEnum: PROC = {
enum: EntitySet ←
IF alphaOrder THEN
DB.DomainSubset[MsgSetDomain, "\000", "\177", First] ELSE
DB.DomainSubset[MsgSetDomain, NIL, NIL, First];
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
msDomainVersion ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
BEGIN ENABLE UNWIND => IF enum#NIL THEN GOTO end;
e: Entity;
FOR e ← DB.NextEntity[enum], DB.NextEntity[enum] UNTIL e = NIL DO
msgSet: MsgSet ←
[DB.EntityInfo[e].name, DB.V2I[DB.GetF[GetMsgSetBasicInfoRel[e], msBIVersion]]];
proc[msgSet];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDB.CarefullyApply[MSEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
};
EnumerateMsgsInSet: PUBLIC PROC [ name: ROPE, fromStart: BOOL TRUE, proc: PROC[msg, TOCentry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] ] RETURNS [msVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum: PROC = {
msI: MSInfo;
enum: RelshipSet;
checkShow: BOOL;
constraint: DB.Constraint;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN {ok← TRUE; RETURN};
checkShow ← name.Equal["Active", FALSE];
constraint ← DB.L2C[LIST[[entity[msI.entity]], [time[]]]];
enum ←
DB.RelationSubset[cdRelation, cdIndex, constraint, IF fromStart THEN First ELSE Last];
BEGIN ENABLE UNWIND => GOTO end;
DO
e: Entity;
rel: Relship = IF fromStart THEN DB.NextRelship[enum] ELSE DB.PrevRelship[enum];
msg, TOCentry: ROPE;
hasBeenRead: BOOL;
startOfSubject: INT;
IF rel = NIL THEN EXIT;
msg ← DB.EntityInfo[e ← DB.V2E[DB.GetF[rel, cdMsg]]].name;
IF checkShow THEN {
showRel: Relship = DB.LookupProperty[mInfo, e];
v: Value = DB.GetF[showRel, mShowIs];
IF v.type # null THEN LOOP;   -- mShowIs is Unaccepted
};
[hasBeenRead, TOCentry, startOfSubject] ← WalnutDB.GetMsgDisplayInfo[e];
proc[msg, TOCentry, hasBeenRead, startOfSubject];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDB.CarefullyApply[MEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSet enumeration"];
};
EnumerateMsgsInMsgSet: PUBLIC PROC[msgSet: MsgSet] RETURNS[lazyEnum: WalnutDB.LazyEnumerator] = {
lazyEnum ← NEW[LazyEnumeratorRec ← [msgSet: msgSet, pos: 0, checkShow: Rope.Equal[msgSet.name, ActiveMsgSetName]]];
lazyEnum.set ← DB.RelationSubset[cdRelation, cdIndex, DB.L2C[LIST[[entity[GetMsgSetEntity[msgSet.name].entity]],[time[]]]]] };
NextMsgInMsgSet: PUBLIC PROC[lazyEnum: WalnutDB.LazyEnumerator] RETURNS[msgID: ROPE, valid: BOOL] = {
howManyIterations: INT ← 0;
GetNextMsg: PROC[] = {
me: Entity;
IF lazyEnum.set = NIL THEN { valid ← FALSE; RETURN };
DO
rel: Relship = DB.NextRelship[lazyEnum.set];
howManyIterations ← howManyIterations+1;
IF rel = NIL THEN { DB.ReleaseRelshipSet[lazyEnum.set]; lazyEnum.set ← NIL; EXIT };
me ← DB.V2E[DB.GetF[rel, cdMsg]];
IF lazyEnum.checkShow THEN {
sRel: Relship = DB.LookupProperty[mInfo, me];
IF NOT DB.NullEntity[DB.V2E[DB.GetF[sRel, mShowIs]]] THEN LOOP ELSE EXIT }
ELSE EXIT
ENDLOOP;
IF me # NIL THEN msgID ← DB.EntityInfo[me].name;
valid ← TRUE };
ResetToStart: PROC[] = {
lazyEnum.set ← DB.RelationSubset[cdRelation, cdIndex, DB.L2C[LIST[[entity[GetMsgSetEntity[lazyEnum.msgSet.name].entity]],[time[]]]]];
FOR i: INT IN [0..lazyEnum.pos) DO [] ← DB.NextRelship[lazyEnum.set] ENDLOOP;
howManyIterations ← 0 };
IF NOT VerifyMsgSet[lazyEnum.msgSet] THEN RETURN[NIL, FALSE];
FOR tryRestart: BOOLTRUE, FALSE WHILE tryRestart DO
failed: BOOLFALSE;
BEGIN
ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN {failed ← TRUE; CONTINUE} ELSE REJECT;
WalnutDB.CarefullyApply[GetNextMsg]
END;
IF NOT failed THEN { lazyEnum.pos ← lazyEnum.pos+howManyIterations; RETURN };
WalnutDB.CarefullyApply[ResetToStart]
ENDLOOP };
EnumerateUnacceptedMsgs: PUBLIC PROC[activeVersion: INT, proc: PROC[msg, TOCentry: ROPE, startOfSubject: INT] ] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
Eum: PROC = {
enum: RelshipSet;
[] ← CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]];
enum ← DB.RelshipsWithEntityField[mInfo, mShowIs, WalnutDB.unacceptedEntity];
BEGIN ENABLE UNWIND => GOTO end;
showRel: Relship;
msg: ROPE;
UNTIL DB.NullRelship[showRel ← DB.NextRelship[enum]] DO
TOCentry: ROPE;
startOfSubject: INT;
me: Entity = DB.V2E[DB.GetF[showRel, mInfoOf]];
msg ← DB.EntityInfo[me].name;
[ , TOCentry, startOfSubject] ← WalnutDB.GetMsgDisplayInfo[me];
proc[msg, TOCentry, startOfSubject];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDB.CarefullyApply[Eum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessable, "During Get New Mail"];
};
AcceptNewMail: PUBLIC PROC[pos: INT, activeVersion: INT] = { -- long running op
Am: PROC = {
rs: RelshipSet;
es: EntitySet;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
accArray: WalnutRegistry.MsgGroup;
numAcc: CARDINAL ← 0;
sinceLastCommit: INT ← 0;
activeRel: Relship = DB.LookupProperty[msBasicInfo, WalnutDB.activeMessageSet];
doMsgGroup: BOOL ← WalnutRegistryPrivate.CheckForMsgGroupRegistration[];
[] ← CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]];
IF doMsgGroup THEN accArray ← ALL[NIL];
BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]};
showRel: Relship;
rs ← DB.RelshipsWithEntityField[
mInfo, mShowIs, WalnutDB.unacceptedEntity];
UNTIL DB.NullRelship[showRel ← DB.NextRelship[rs]] DO
me: Entity = DB.V2E[DB.GetF[showRel, mInfoOf]];
DB.SetF[showRel, mShowIs, nullValue];
IF doMsgGroup THEN {
accArray[numAcc] ← DB.EntityInfo[me].name;
numAcc ← numAcc + 1;
};
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency THEN {
newCount: INT = DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit;
DB.SetF[activeRel, msBICount, DB.I2V[newCount]];
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
IF doMsgGroup THEN {
WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray];
accArray ← ALL[NIL];
numAcc ← 0;
};
};
ENDLOOP;
DB.ReleaseRelshipSet[rs];
END;
BEGIN ENABLE UNWIND => {IF es#NIL THEN DB.ReleaseEntitySet[es]};
se: Entity;
es ← DB.DomainSubset[ServerDomain, NIL, NIL, First];
FOR se ← DB.NextEntity[es], DB.NextEntity[es] UNTIL se = NIL DO
rel: Relship = DB.LookupProperty[sBasicInfo, se];
DB.SetF[rel, sBINum, DBZeroInt];
ENDLOOP;
DB.ReleaseEntitySet[es];
END;
IF sinceLastCommit # 0 THEN
DB.SetF[activeRel, msBICount, DB.I2V[DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit]];
DB.SetF[activeRel, msBIVersion, DB.I2V[DB.V2I[DB.GetF[activeRel, msBIVersion]] + 1]];
DB.SetF[DB.FirstRelship[gNewMailInfo], gAcceptNewMailLogPos, DB.I2V[pos]];
DB.SetF[DB.FirstRelship[gLogInfo], gOpInProgressPos, DBMinusOneInt];
WalnutRoot.CommitAndContinue[];
IF doMsgGroup AND (numAcc # 0) THEN
WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray];
};
WalnutDB.CarefullyApply[Am];
};
Internal procedures
mismatchReport: ROPE = "Msgset: %g: version is %g, version expected is: %g";
GetMsgSetBasicInfoRel: PROC[ms: Entity] RETURNS[rel: Relship] = INLINE
{ RETURN[DB.LookupProperty[msBasicInfo, ms]] };
GetMsgEntity: PROC[msg: ROPE] RETURNS[e: IsEntity] = {
e.entity ← DB.LookupEntity[MsgDomain, msg];
e.exists ← (e.entity # NIL);
};
GetMsgSetAndVersion: PROC[name: ROPE] RETURNS[msI: MSInfo, version: INT] = {
msI ← GetMsgSetEntity[name];
IF msI # NIL THEN version ← DB.V2I[DB.GetF[msI.versionRel, msBIVersion]]
ELSE version← -1;
};
GetMsgSetEntity: PROC[name: ROPE] RETURNS[msInfo: MSInfo] = {
aName: ATOM = CanonicalName[name];  -- all lower case
cName: ROPE;
found: BOOL;
val: RefTab.Val;
mse: Entity;
IF WalnutDB.msgSetsTable = NIL THEN {
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
numMsgSets: INTDB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
WalnutDB.msgSetsTable ← RefTab.Create[MAX[numMsgSets*2+1, 16]];
};
[found, val] ← RefTab.Fetch[WalnutDB.msgSetsTable, aName];
IF found THEN {
msInfo ← NARROW[val];
IF ~DB.NullEntity[msInfo.entity] THEN RETURN;
msInfo.entity ← DB.LookupEntity[MsgSetDomain, msInfo.canonicalName];
IF msInfo.entity = NIL THEN {  -- no longer exists
[] ← RefTab.Delete[WalnutDB.msgSetsTable, aName];
RETURN[NIL]
};
msInfo.versionRel ← GetMsgSetBasicInfoRel[msInfo.entity];
RETURN;
};
mse ← DB.LookupEntity[MsgSetDomain, cName ← Atom.GetPName[aName]];
IF mse = NIL THEN RETURN;  -- return NIL IF msgSet doesn't exist
msInfo ← NEW[MSInfoObject ← [canonicalName: cName, entity: mse]];
msInfo.versionRel ← GetMsgSetBasicInfoRel[mse];
[] ← RefTab.Store[WalnutDB.msgSetsTable, aName, msInfo];
};
changes name to all lower case, to get canonical names
CanonicalName: PUBLIC PROC[name: ROPE] RETURNS[aName: ATOM] = {
nameText.length ← 0;
FOR i: INT IN [0 .. name.Length[]) DO
[] ← RefText.InlineAppendChar[nameText, Ascii.Lower[name.Fetch[i]]];
ENDLOOP;
aName ← Atom.MakeAtomFromRefText[nameText];
};
CheckDomainVersion: PROC[version: INT] = {
is: INT;
rVersionInfo: Relship;
IF version = dontCareDomainVersion THEN RETURN;
rVersionInfo ← DB.FirstRelship[gVersionInfo];
IF version # (is ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]]) THEN {
DB.SetF[DB.FirstRelship[gLogInfo], gOpInProgressPos, DBMinusOneInt];
ERROR WalnutDefs.VersionMismatch[
IO.PutFR["Domain version is %g, version expected is: %g", IO.int[is], IO.int[version]] ];
};
};
CheckMsgSetVersion: PROC[msgSet: MsgSet] RETURNS[msI: MSInfo, version: INT] = {
[msI, version] ← GetMsgSetAndVersion[msgSet.name];
IF msI = NIL THEN RETURN;
IF msgSet.version = dontCareMsgSetVersion THEN RETURN;
IF msgSet.version = version THEN RETURN;
DB.SetF[DB.FirstRelship[gLogInfo], gOpInProgressPos, DBMinusOneInt];
ERROR WalnutDefs.VersionMismatch[IO.PutFR[mismatchReport,
IO.rope[msgSet.name], IO.int[version], IO.int[msgSet.version]] ];
};
CheckMsgSetEntity: PROC[msgSet: MsgSet] RETURNS[msI: MSInfo] = {
is: INT;
msI ← GetMsgSetEntity[msgSet.name];
IF msI = NIL THEN RETURN;
IF msgSet.version = dontCareMsgSetVersion THEN RETURN;
IF msgSet.version = (is ← DB.V2I[DB.GetF[msI.versionRel, msBIVersion]]) THEN RETURN;
DB.SetF[DB.FirstRelship[gLogInfo], gOpInProgressPos, DBMinusOneInt];
ERROR WalnutDefs.VersionMismatch[IO.PutFR[mismatchReport,
IO.rope[msgSet.name], IO.int[is], IO.int[msgSet.version]] ];
};
EmptyThisMsgSet: PROC[mse: Entity, name: ROPE, report: CheckReportProc]
RETURNS[someInDeleted: BOOL] = {
rs: RelshipSet = DB.RelshipsWithEntityField[cdRelation, cdMsgSet, mse];
BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]};
rel: Relship;
de: Entity = WalnutDB.deletedMessageSet;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
delArray: WalnutRegistry.MsgGroup;
remArray: WalnutRegistry.MsgGroup;
numDel: CARDINAL ← 0;
numRem: CARDINAL ← 0;
sinceLastCommit: INT ← 0;
count: INT ← 0;
someInDeleted ← FALSE;
UNTIL DB.NullRelship[rel← DB.NextRelship[rs]] DO
me: Entity = DB.V2E[DB.GetF[rel, cdMsg]];
rs2: RelshipSet;
deleted: BOOL;
DB.DestroyRelship[rel];
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs2];
rs2 ← DB.RelshipsWithEntityField[cdRelation, cdMsg, me];
deleted ← DB.NullRelship[DB.NextRelship[rs2]];
DB.ReleaseRelshipSet[rs2];
END;
IF deleted THEN {
[] ← AddMsgTo[me, WalnutDB.deletedMessageSet];
delArray[numDel] ← DB.EntityInfo[me].name;
numDel ← numDel + 1;
}
ELSE {
remArray[numRem] ← DB.EntityInfo[me].name;
numRem ← numRem + 1;
};
someInDeleted← someInDeleted OR deleted;
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency THEN {
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
FOR i: CARDINAL IN [0 .. numDel) DO
WalnutRegistryPrivate.NotifyForMsgEvent[deleted, delArray[i]]; ENDLOOP;
FOR i: CARDINAL IN [0 .. numRem) DO
WalnutRegistryPrivate.NotifyForMove[msg: remArray[i], to: NIL, from: name];
ENDLOOP;
numDel ← numRem ← 0;
};
IF report # NIL THEN count ← CheckCount[count, report];
ENDLOOP;
DB.ReleaseRelshipSet[rs];
IF sinceLastCommit # 0 THEN {
WalnutRoot.CommitAndContinue[];
FOR i: CARDINAL IN [0 .. numDel) DO
WalnutRegistryPrivate.NotifyForMsgEvent[deleted, delArray[i]]; ENDLOOP;
FOR i: CARDINAL IN [0 .. numRem) DO
WalnutRegistryPrivate.NotifyForMove[msg: remArray[i], to: NIL, from: name];
ENDLOOP;
};
END;
};
ChangeGlobalMsgSetInfo: PROC[delta: INT] = {
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
numNow: INTDB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
newV: INT = DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]] + 1;
DB.SetF[rVersionInfo, gMsgSetsVersion, DB.I2V[newV]];
DB.SetF[rVersionInfo, gMsgSetCount, DB.I2V[numNow + delta]];
};
AddMsgTo: PROC[me: Entity, mse: Entity] = {
date: Value = DB.GetF[DB.LookupProperty[mInfo, me], mDateIs];
val: DB.ValueSequence = DB.L2VS[LIST[DB.E2V[me], DB.E2V[mse], date]];
[] ← DB.CreateRelship[cdRelation, val];
WalnutDB.ChangeCountInMsgSet[mse, 1]
};
CheckCount: PROC[count: INT, report: CheckReportProc] RETURNS[c: INT] = {
IF count = 0 THEN report["\n"];  -- put out CR first
IF (c ← count + 1) MOD 10 # 0 THEN RETURN;
IF c MOD 100 = 0 THEN report["!"] ELSE report["~"];
};
END.