LoganQueryClassImpl.mesa
Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved.
Doug Terry, July 22, 1992 2:28 pm PDT
Willie-s, April 27, 1992 11:40 am PDT
LoganQuery allows more complicated queries to be performed on a LoganBerry database than the simple retrievals provided by the LoganBerry interface. Database queries of various classes can be registered; retrieval operations are class-specific. For instance, the $Filter class returns database entries that match a particular pattern, while the $Merger class merges the entries returned by two independent queries. The $Query class cascades filters to perform multiple-attribute queries. The predefined $Simple class permits operations identical to those provided by LoganBerry. Clients are free to create their own class of queries.
DIRECTORY
Process USING [Abort, CheckForAbort, GetCurrent, InvalidProcess],
Rope USING [Compare, Equal, ROPE],
LoganBerry,
LoganBerryEntry USING [GetAttr],
LoganQueryClass;
LoganQueryClassImpl: CEDAR PROGRAM
IMPORTS Process, Rope, LoganBerry, LoganBerryEntry
EXPORTS LoganQueryClass
~ BEGIN OPEN LoganQueryClass;
ROPE: TYPE ~ Rope.ROPE;
Basic operations
NextEntry: PROC [cursor: Cursor, dir: CursorDirection ¬ increasing] RETURNS [entry: LoganBerry.Entry] = {
Retrieves the next entry relative to the given cursor. The cursor is automatically updated so that NextEntry may be repeatedly called to enumerate entries. NIL is returned if the cursor is at the end of the sequence and dir=increasing or at the start of the sequence and dir=decreasing.
Errors: IndexNotOpen, BadIndex, LogNotOpen, BadLogEntry
IF cursor.class.retrieve = NIL THEN RETURN[NIL];
Process.CheckForAbort[];
RETURN[cursor.class.retrieve[cursor, dir]];
};
EndGenerate: PROC [cursor: Cursor] RETURNS [] = {
Releases the cursor; no further operations may be performed using the given cursor. This must be called once for every cursor created.
IF cursor.class.destroy = NIL THEN RETURN;
cursor.class.destroy[cursor];
};
$Simple query class
SimpleClass: QueryClass = NEW[QueryClassRec ¬ [$Simple, SimpleRetrieve, SimpleDestroy]];
SimpleCursor: TYPE = REF LoganBerry.Cursor;
SimpleRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
sc: SimpleCursor => RETURN[LoganBerry.NextEntry[cursor: sc­, dir: dir]];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Simple cursor"];
};
SimpleDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
sc: SimpleCursor => LoganBerry.EndGenerate[cursor: sc­];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Simple cursor"];
};
GenerateEntries: PUBLIC PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue ¬ NIL, end: LoganBerry.AttributeValue ¬ NIL] RETURNS [cursor: Cursor] = {
Identical to the GenerateEntries operation provided by LoganBerry but returns a Cursor with class=$Simple and data=LoganBerry.Cursor.
Errors: LoganBerry.NoIndex
sc: SimpleCursor ¬ NEW[LoganBerry.Cursor];
sc­ ¬ LoganBerry.GenerateEntries[db: db, key: key, start: start, end: end];
cursor.class ¬ SimpleClass;
cursor.data ¬ sc;
};
$Filter query class
FilterClass: QueryClass = NEW[QueryClassRec ¬ [$Filter, FilterRetrieve, FilterDestroy]];
FilterCursor: TYPE = REF FilterCursorRec;
FilterCursorRec: TYPE = RECORD [
input: Cursor,
pattern: ROPE,
pparsed: REF ¬ NIL,
filter: MatchProc,
atype: LoganBerry.AttributeType,
stopNG: BOOLEAN ¬ FALSE
];
FilterRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
fc: FilterCursor => {
match, nothingGreater: BOOLEAN ¬ FALSE;
numAtypes: INT;
entry ¬ NextEntry[fc.input, dir];
UNTIL match OR entry = NIL DO
could be multiple attributes of fc.atype per entry so check them all
numAtypes ¬ 0;
FOR e: LoganBerry.Entry ¬ entry, e.rest WHILE e # NIL DO
IF e.first.type = fc.atype THEN {
numAtypes ¬ numAtypes + 1;
[match, nothingGreater, fc.pparsed] ¬ fc.filter[e.first.value, fc.pattern, fc.pparsed];
IF match THEN EXIT;
};
ENDLOOP;
IF NOT match THEN
IF nothingGreater AND fc.stopNG AND dir = increasing AND numAtypes = 1 THEN
entry ¬ NIL -- can stop before actual end of input
ELSE
entry ¬ NextEntry[fc.input, dir];
ENDLOOP;
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Filter cursor"];
};
FilterDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
fc: FilterCursor => EndGenerate[fc.input];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Filter cursor"];
};
FilterEntries: PUBLIC PROC [input: Cursor, pattern: ROPE, filter: MatchProc, atype: LoganBerry.AttributeType, stopIfNothingGreater: BOOLEAN ¬ FALSE] RETURNS [output: Cursor] = {
Returns a Cursor of class $Filter. Retrievals using this new cursor apply the given pattern-matching filter to the appropriate attribute of entries identified by the input cursor. A retrieval returns NIL if the input is exhausted or stopIfNothingGreater=TRUE and the filter procedure returns nothingGreater=TRUE.
fc: FilterCursor ¬ NEW[FilterCursorRec];
IF filter = NIL THEN
ERROR LoganBerry.Error[$BadCursor, "NIL filter proc provided"];
fc.input ¬ input;
fc.pattern ¬ pattern;
fc.filter ¬ filter;
fc.atype ¬ atype;
fc.stopNG ¬ stopIfNothingGreater;
output.class ¬ FilterClass;
output.data ¬ fc;
};
$Merger query class
MergerClass: QueryClass = NEW[QueryClassRec ¬ [$Merger, MergerRetrieve, MergerDestroy]];
MergerCursor: TYPE = REF MergerCursorRec;
MergerCursorRec: TYPE = RECORD [
input: ARRAY [0..1] OF Cursor,
atype: LoganBerry.AttributeType,
prefetch: BOOLEAN ¬ FALSE, -- is there a prefetched entry?
prefetchEntry: LoganBerry.Entry, -- prefetched entry if one exists
prefetchValue: LoganBerry.AttributeValue, -- value saved for performance reasons
prefetchInput: [0..1] ¬ 0, -- which input the prefetch came from
prefetchDir: CursorDirection ¬ increasing -- direction of prefetch
];
MergerRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
mc: MergerCursor => {
newInput: [0..1];
newEntry: LoganBerry.Entry;
newValue: LoganBerry.AttributeValue;
returnNew: BOOLEAN;
IF mc.prefetch AND NOT mc.prefetchDir = dir THEN { -- prefetched in wrong direction
so move cursor back over prefetched entry
[] ¬ NextEntry[mc.input[mc.prefetchInput], dir];
mc.prefetch ¬ FALSE;
};
IF NOT mc.prefetch THEN { -- go ahead and get an entry
mc.prefetchEntry ¬ NextEntry[mc.input[0], dir];
mc.prefetchValue ¬ LoganBerryEntry.GetAttr[mc.prefetchEntry, mc.atype];
mc.prefetchInput ¬ 0;
mc.prefetchDir ¬ dir;
mc.prefetch ¬ TRUE;
};
newInput ¬ ABS[1-mc.prefetchInput];
newEntry ¬ NextEntry[mc.input[newInput], dir];
IF mc.prefetchEntry = NIL THEN { -- one input already exhausted
IF newEntry = NIL THEN -- exhausted both inputs so reset state
mc.prefetch ¬ FALSE;
RETURN[newEntry];
};
IF newEntry = NIL THEN { -- just exhausted input
entry ¬ mc.prefetchEntry;
mc.prefetchEntry ¬ NIL;
mc.prefetchInput ¬ newInput;
RETURN[entry];
};
newValue ¬ LoganBerryEntry.GetAttr[newEntry, mc.atype];
returnNew ¬ SELECT Rope.Compare[newValue, mc.prefetchValue, FALSE] FROM
less => (dir = increasing),
greater => (dir = decreasing),
ENDCASE => TRUE;
IF returnNew THEN {
entry ¬ newEntry;
}
ELSE {
entry ¬ mc.prefetchEntry;
mc.prefetchEntry ¬ newEntry;
mc.prefetchValue ¬ newValue;
mc.prefetchInput ¬ newInput;
};
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Merger cursor"];
};
MergerDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
mc: MergerCursor => {
EndGenerate[mc.input[0]];
EndGenerate[mc.input[1]];
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Merger cursor"];
};
MergeEntries: PUBLIC PROC [input1, input2: Cursor, atype: LoganBerry.AttributeType] RETURNS [output: Cursor] = {
Returns a Cursor of class $Merger. Retrievals using this new cursor merge the two input streams. The input streams should be ordered by the given attribute type. A retrieval returns NIL only when both inputs are exhausted.
mc: MergerCursor ¬ NEW[MergerCursorRec];
mc.input[0] ¬ input1;
mc.input[1] ¬ input2;
mc.atype ¬ atype;
mc.prefetch ¬ FALSE;
output.class ¬ MergerClass;
output.data ¬ mc;
};
$AntiFilter query class
Shares a FilterCursor record type and the DestroyProc with the $Filter class.
AntiFilterClass: QueryClass = NEW[QueryClassRec ¬ [$AntiFilter, AntiFilterRetrieve, FilterDestroy]];
AntiFilterRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
fc: FilterCursor => {
match, nothingGreater: BOOLEAN ¬ TRUE;
numAtypes: INT;
entry ¬ NextEntry[fc.input, dir];
UNTIL NOT match OR entry = NIL DO
initialize match to FALSE so will return entry if no attributes of given type
match ¬ FALSE;
could be multiple attributes of fc.atype per entry so check them all
numAtypes ¬ 0;
FOR e: LoganBerry.Entry ¬ entry, e.rest WHILE e # NIL DO
IF e.first.type = fc.atype THEN {
numAtypes ¬ numAtypes + 1;
[match, nothingGreater, fc.pparsed] ¬ fc.filter[e.first.value, fc.pattern, fc.pparsed];
IF match THEN EXIT;
};
ENDLOOP;
IF match THEN
entry ¬ NextEntry[fc.input, dir];
ENDLOOP;
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Filter cursor"];
};
AntiFilterEntries: PUBLIC PROC [input: Cursor, pattern: ROPE, filter: MatchProc, atype: LoganBerry.AttributeType] RETURNS [output: Cursor] = {
Returns a Cursor of class $AntiFilter. Retrievals using this new cursor apply the given pattern-matching filter to the appropriate attribute of entries identified by the input cursor and return those entries that do NOT match the pattern. In other words, the resulting cursor returns exactly the opposite of what would be returned by a $Filter cursor. A retrieval returns NIL if the input is exhausted.
fc: FilterCursor ¬ NEW[FilterCursorRec];
IF filter = NIL THEN
ERROR LoganBerry.Error[$BadCursor, "NIL filter proc provided"];
fc.input ¬ input;
fc.pattern ¬ pattern;
fc.filter ¬ filter;
fc.atype ¬ atype;
fc.stopNG ¬ FALSE;
output.class ¬ AntiFilterClass;
output.data ¬ fc;
};
$Duplicator query class
DuplicatorClass: QueryClass = NEW[QueryClassRec ¬ [$Duplicator, DuplicatorRetrieve, DuplicatorDestroy]];
DuplicatorCursor: TYPE = REF DuplicatorCursorRec;
DuplicatorCursorRec: TYPE = RECORD [
input: Cursor,  -- input cursor
num: [0..1],   -- which output cursor am I?
shared: SharedDuplicateData -- stuff common to both output queues
];
SharedDuplicateData: TYPE = REF SharedDuplicateDataRec;
SharedDuplicateDataRec: TYPE = RECORD [
closed: ARRAY [0..1] OF BOOLEAN ¬ ALL[FALSE], -- cursor destroyed?
prefetched: ARRAY [0..1] OF LIST OF LoganBerry.Entry ¬ ALL[NIL] -- entries ready for output cursors
];
DuplicatorRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
dc: DuplicatorCursor => {
other: [0..1] ¬ IF dc.num=0 THEN 1 ELSE 0;
IF dc.shared.prefetched[dc.num] = NIL THEN {
Get entry off input queue
entry ¬ NextEntry[dc.input, dir];
IF entry # NIL AND NOT dc.shared.closed[other] THEN {
Add entry to other output cursor's prefetched list
IF dc.shared.prefetched[other] = NIL
THEN {
dc.shared.prefetched[other] ¬ LIST[entry];
}
ELSE {
eList: LIST OF LoganBerry.Entry ¬ dc.shared.prefetched[other];
UNTIL eList.rest = NIL DO eList ¬ eList.rest; ENDLOOP;
eList.rest ¬ LIST[entry];
};
};
}
ELSE {
Get entry off prefetched list
entry ¬ dc.shared.prefetched[dc.num].first;
dc.shared.prefetched[dc.num] ¬ dc.shared.prefetched[dc.num].rest;
};
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Duplicator cursor"];
};
DuplicatorDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
dc: DuplicatorCursor => {
dc.shared.closed[dc.num] ¬ TRUE;
IF dc.shared.closed[0] AND dc.shared.closed[1] THEN
EndGenerate[dc.input];
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Duplicator cursor"];
};
DuplicateEntries: PUBLIC PROC [input: Cursor] RETURNS [output1, output2: Cursor] = {
Returns a pair of Cursors of class $Duplicator. Retrievals using either new cursor return the same entries as the input cursor. That is, each entry from the input cursor is copied to both output cursors.
dc: DuplicatorCursor;
sd: SharedDuplicateData ¬ NEW[SharedDuplicateDataRec];
sd.prefetched[0] ¬ NIL;
sd.prefetched[1] ¬ NIL;
dc ¬ NEW[DuplicatorCursorRec];
dc.num ¬ 0;
dc.input ¬ input;
dc.shared ¬ sd;
output1.class ¬ DuplicatorClass;
output1.data ¬ dc;
dc ¬ NEW[DuplicatorCursorRec];
dc.num ¬ 1;
dc.input ¬ input;
dc.shared ¬ sd;
output2.class ¬ DuplicatorClass;
output2.data ¬ dc;
};
$UnDuplicator query class
UnDuplicatorClass: QueryClass = NEW[QueryClassRec ¬ [$UnDuplicator, UnDuplicatorRetrieve, UnDuplicatorDestroy]];
UnDuplicatorCursor: TYPE = REF UnDuplicatorCursorRec;
UnDuplicatorCursorRec: TYPE = RECORD [
input: Cursor,  -- input cursor
atype: LoganBerry.AttributeType,
prev: LoganBerry.AttributeValue ¬ NIL -- previous attribute value
];
UnDuplicatorRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
udc: UnDuplicatorCursor => {
value: LoganBerry.AttributeValue;
DO -- until get new value
entry ¬ NextEntry[udc.input, dir];
value ¬ LoganBerryEntry.GetAttr[entry, udc.atype];
IF entry = NIL OR NOT Rope.Equal[value, udc.prev, FALSE] THEN EXIT;
ENDLOOP;
udc.prev ¬ value;
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $UnDuplicator cursor"];
};
UnDuplicatorDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
udc: UnDuplicatorCursor => EndGenerate[udc.input];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $UnDuplicator cursor"];
};
UnDuplicateEntries: PUBLIC PROC [input: Cursor, atype: LoganBerry.AttributeType] RETURNS [output: Cursor] = {
Returns a Cursor of class $UnDuplicator. Retrievals using the new cursor will suppress, i.e. not return, successive entries with the same value for the given attribute type. Typically, this is used in conjunction with MergeEntries to combine two cursors and filter out duplicates; for instance UnDuplicateEntries[MergeEntries[DuplicateEntries[c]]] should produce a cursor whose output is equivalent to the orginal one. The input stream should be ordered by the given attribute type and this attribute should be the primary key.
udc: UnDuplicatorCursor ¬ NEW[UnDuplicatorCursorRec];
udc.input ¬ input;
udc.atype ¬ atype;
output.class ¬ UnDuplicatorClass;
output.data ¬ udc;
};
$Subtracter query class
SubtracterClass: QueryClass = NEW[QueryClassRec ¬ [$Subtracter, SubtracterRetrieve, SubtracterDestroy]];
SubtracterCursor: TYPE = REF SubtracterCursorRec;
SubtracterCursorRec: TYPE = RECORD [
input: Cursor,  -- input cursor
except: Cursor,  -- exceptions
ptype: LoganBerry.AttributeType, -- primary key
atype: LoganBerry.AttributeType, -- attribute on which streams are ordered
avalue: LoganBerry.AttributeValue ¬ NIL, -- attribute value for current batch
exceptEntry: LoganBerry.Entry ¬ NIL, -- next entry from except cursor
exceptBatch: LIST OF LoganBerry.Entry ¬ NIL, -- more entries from except cursor
nextExceptEntry: LoganBerry.Entry ¬ NIL -- first entry from next batch on except cursor
];
Note: whenever we retrieve one entry off of the except cursor, we get all of them with the same value for atype, i.e. avalue. This group is called a "batch". In the common case where atype = ptype, all batches are of size one. In this case, exceptEntry stores the single entry from the current batch. If a batch is larger than one entry, then all of the entries for the batch are stored in the list exceptBatch; in this case, the first entry on exceptBatch is the same as the exceptEntry. The nextExceptEntry always stores the first (and possibly only) entry from the next batch.
NextBatch: PROC [sc: SubtracterCursor, dir: CursorDirection] RETURNS [first: LoganBerry.Entry] ~ {
sc.exceptEntry ¬ sc.nextExceptEntry;
sc.nextExceptEntry ¬ NIL;
sc.exceptBatch ¬ NIL;
IF sc.exceptEntry = NIL THEN {
sc.exceptEntry ¬ NextEntry[sc.except, dir];
IF sc.exceptEntry = NIL THEN RETURN[NIL];
};
sc.avalue ¬ LoganBerryEntry.GetAttr[sc.exceptEntry, sc.atype];
sc.nextExceptEntry ¬ NextEntry[sc.except, dir];
IF sc.ptype # sc.atype AND sc.nextExceptEntry # NIL THEN { -- get complete batch
tail: LIST OF LoganBerry.Entry ¬ NIL;
WHILE Rope.Equal[LoganBerryEntry.GetAttr[sc.nextExceptEntry, sc.atype], sc.avalue, FALSE] DO
Add sc.nextExceptEntry to exceptBatch list
IF sc.exceptBatch = NIL THEN {
sc.exceptBatch ¬ LIST[sc.exceptEntry];
tail ¬ sc.exceptBatch;
};
tail.rest ¬ LIST[sc.nextExceptEntry];
tail ¬ tail.rest;
sc.nextExceptEntry ¬ NextEntry[sc.except, dir];
ENDLOOP;
};
RETURN[sc.exceptEntry];
};
DiscardFromBatch: PROC [sc: SubtracterCursor, discard: LoganBerry.Entry] RETURNS [] ~ {
prev, ptr: LIST OF LoganBerry.Entry;
IF sc.exceptBatch = NIL THEN {
IF sc.exceptEntry = discard THEN sc.exceptEntry ¬ NIL;
RETURN;
};
prev ¬ NIL;
ptr ¬ sc.exceptBatch;
WHILE ptr # NIL AND NOT ptr.first = discard DO
prev ¬ ptr;
ptr ¬ ptr.rest;
ENDLOOP;
IF ptr # NIL THEN {
IF prev = NIL
THEN {
sc.exceptBatch ¬ sc.exceptBatch.rest;
sc.exceptEntry ¬ sc.exceptBatch.first;
}
ELSE {
prev.rest ¬ ptr.rest;
};
};
};
SubtracterRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
sc: SubtracterCursor => {
IF sc.exceptEntry = NIL THEN
sc.exceptEntry ¬ NextBatch[sc, dir];
entry ¬ NextEntry[sc.input, dir];
WHILE sc.exceptEntry # NIL DO -- until get entry that should not be discarded
entryAValue: LoganBerry.AttributeValue ¬ LoganBerryEntry.GetAttr[entry, sc.atype];
SELECT Rope.Compare[entryAValue, sc.avalue, FALSE] FROM
less => EXIT; -- return entry
equal => { -- need to check primary keys
match: LoganBerry.Entry ¬ NIL;
SELECT TRUE FROM
sc.ptype = sc.atype => match ¬ sc.exceptEntry;
sc.exceptBatch = NIL => {
exceptPValue, entryPValue: LoganBerry.AttributeValue;
exceptPValue ¬ LoganBerryEntry.GetAttr[sc.exceptEntry, sc.ptype];
entryPValue ¬ LoganBerryEntry.GetAttr[entry, sc.ptype];
IF exceptPValue = entryPValue THEN match ¬ sc.exceptEntry;
};
ENDCASE => {
Hard case: the except stream is not ordered by primary key and contains several entries with the same secondary attribute; we compare them all to the current entry.
entryPValue: LoganBerry.AttributeValue ¬ LoganBerryEntry.GetAttr[entry, sc.ptype];
FOR eList: LIST OF LoganBerry.Entry ¬ sc.exceptBatch, eList.rest WHILE eList # NIL DO
exceptPValue: LoganBerry.AttributeValue ¬ LoganBerryEntry.GetAttr[eList.first, sc.ptype];
IF exceptPValue = entryPValue THEN {match ¬ eList.first; EXIT};
ENDLOOP;
};
IF match = NIL THEN EXIT; -- return entry
Discard entries from both cursors and try again
entry ¬ NextEntry[sc.input, dir];
DiscardFromBatch[sc, match];
IF sc.exceptEntry = NIL THEN
sc.exceptEntry ¬ NextBatch[sc, dir];
};
greater => { -- discard except batch and try again
sc.exceptEntry ¬ NextBatch[sc, dir];
};
ENDCASE;
ENDLOOP;
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Subtracter cursor"];
};
SubtracterDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
sc: SubtracterCursor => EndGenerate[sc.input];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Subtracter cursor"];
};
SubtractEntries: PUBLIC PROC [input, except: Cursor, ptype, atype: LoganBerry.AttributeType] RETURNS [output: Cursor] = {
Returns a Cursor of class $Subtracter. Retrievals using the new cursor will return all entries from the input cursor except for those returned by the except cursor. The input and except cursors should both be ordered by the atype attribute; ptype should indicate the primary key, which is used to determine the equivalence of two entries.
sc: SubtracterCursor ¬ NEW[SubtracterCursorRec];
sc.input ¬ input;
sc.except ¬ except;
sc.ptype ¬ ptype;
sc.atype ¬ atype;
output.class ¬ SubtracterClass;
output.data ¬ sc;
};
$Feeder query class
FeederClass: QueryClass = NEW[QueryClassRec ¬ [$Feeder, FeederRetrieve, FeederDestroy]];
FeederCursor: TYPE = REF FeederCursorRec;
FeederCursorRec: TYPE = RECORD [
input: Cursor  -- input cursor
];
FeederRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data SELECT FROM
fc: FeederCursor => {
entry ¬ NIL;
IF fc.input.class # NIL THEN
entry ¬ NextEntry[fc.input, dir];
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Feeder cursor"];
};
FeederDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
fc: FeederCursor => EndGenerate[fc.input];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Feeder cursor"];
};
FeedEntries: PUBLIC PROC [] RETURNS [output: Cursor] = {
Returns a Cursor of class $Feeder. Such a cursor simply maps its input to its output. The utility is that its input cursor can be supplied later and changed at will.
fc: FeederCursor ¬ NEW[FeederCursorRec];
fc.input.class ¬ NIL;
output.class ¬ FeederClass;
output.data ¬ fc;
};
SetFeederInput: PUBLIC PROC [feeder, input: Cursor] = {
Sets (or changes) the input cursor for the feeder.
WITH feeder.data SELECT FROM
fc: FeederCursor => fc.input ¬ input;
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Feeder cursor"];
};
$Aborter query class
AborterClass: QueryClass = NEW[QueryClassRec ¬ [$Aborter, AborterRetrieve, AborterDestroy]];
AborterCursor: TYPE = REF AborterCursorRec;
AborterCursorRec: TYPE = RECORD [
input: Cursor,
aborted: BOOLEAN ¬ FALSE, -- if TRUE then cursor has been aborted
process: PROCESS ¬ NIL -- process currently executing retrieve operation
];
AborterRetrieve: RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
ENABLE ABORTED => GOTO Aborted;
WITH cursor.data SELECT FROM
ac: AborterCursor => {
IF ac.aborted THEN GOTO Aborted;
TRUSTED {ac.process ¬ LOOPHOLE[Process.GetCurrent[], PROCESS];};
entry ¬ NextEntry[ac.input, dir];
ac.process ¬ NIL;
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Aborter cursor"];
EXITS
Aborted => RETURN[NIL];
};
AborterDestroy: DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data SELECT FROM
ac: AborterCursor => EndGenerate[ac.input];
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Aborter cursor"];
};
EnableAborts: PUBLIC PROC [input: Cursor] RETURNS [output: Cursor] ~ {
Returns a Cursor of class $Aborter. Operations on cursors of this class behave exactly like those on the input cursor except that they can be aborted using the AbortQuery operation defined below.
ac: AborterCursor ¬ NEW[AborterCursorRec];
ac.input ¬ input;
output.class ¬ AborterClass;
output.data ¬ ac;
};
AbortQuery: PUBLIC PROC [cursor: Cursor] RETURNS [] ~ TRUSTED {
If a process is currently executing a NextEntry operation on the given cursor, the process is aborted and the operation returns NIL; any subsequent calls to NextEntry will also return NIL (i.e. the cursor is no longer useable). This operation can only be used for cursors of class $Aborter.
Note: there are possible race conditions between AbortQuery and AborterRetrieve, but none are very bad.
WITH cursor.data SELECT FROM
ac: AborterCursor => {
ac.aborted ¬ TRUE;
IF ac.process # NIL THEN
Process.Abort[ac.process ! Process.InvalidProcess => CONTINUE];
};
ENDCASE => NULL;
};
END.
Doug Terry November 7, 1985 3:11:59 pm PST
created
changes to: DIRECTORY, ILQueryImpl, EXPORTS, ~, NextEntry, EndGenerate, SimpleRetrieve, SimpleDestroy, GenerateEntries, FilterEntries, MergeEntries, Equal, SimpleRetrieve, FilterClass, FilterCursorRec, FilterRetrieve, FilterDestroy, FilterEntries, FilterCursor, FilterCursorRec, Soundex, MergerClass, MergerRetrieve, MergerDestroy, Prefix, Wildcard, RE
Doug Terry, November 8, 1985 7:36:34 pm PST
changes to: Wildcard, SubMatch (local of Wildcard), RE, Soundex, MergerCursorRec, MergerRetrieve, DIRECTORY, IMPORTS, Equal, Prefix, NextEntry, EndGenerate, GenerateEntries, FilterEntries, MergeEntries, FilterRetrieve
Doug Terry, November 26, 1985 6:15:38 pm PST
changes to: DIRECTORY, LoganQueryImpl, IMPORTS, EXPORTS, ~, NextEntry, SimpleRetrieve, SimpleDestroy, GenerateEntries, FilterCursorRec, FilterRetrieve, FilterEntries, MergerCursorRec, MergerRetrieve, MergeEntries, GetAttributeValue, FilterDestroy, MergerDestroy
Doug Terry, December 17, 1985 11:15:09 am PST
changes to: FilterRetrieve, finderCache, RE
Doug Terry, December 23, 1985 1:58:05 pm PST
changes to: SubMatch (local of Wildcard), finderCache
Doug Terry, January 23, 1986 8:38:31 pm PST
changes to: SimpleRetrieve, SimpleDestroy, GenerateEntries
Doug Terry, February 12, 1986 11:06:34 am PST
changes to: SubMatch (local of Wildcard)
Doug Terry, April 18, 1986 5:45:39 pm PST
changes to: DIRECTORY, IMPORTS
Doug Terry, December 15, 1986 6:13:39 pm PST
NextEntry now calls Process.CheckForAbort.
changes to: DIRECTORY, IMPORTS, NextEntry
Doug Terry, April 15, 1987 11:03:13 am PDT
Moved FilterProcs to PatternMatchImpl; added $Query class from LoganBerryBrowserImpl.
changes to: QueryEntries, AnalyzeSubPlan, CheckPattern, DIRECTORY, FilterEntries, GetAttributeValue, ParseSubrange, IMPORTS, ~, FilterCursorRec, FilterRetrieve, fc (local of FilterRetrieve), MergerCursorRec, mc (local of MergerRetrieve), AbortQuery
Doug Terry, April 17, 1987 11:13:14 am PDT
Added special $Aborter class.
changes to: NextEntry, FilterCursorRec, FilterRetrieve, fc (local of FilterRetrieve), MergerCursorRec, MergerRetrieve, mc (local of MergerRetrieve), AborterClass, AborterCursor, AborterCursorRec, AborterRetrieve, ac (local of AborterRetrieve), AborterDestroy, EnableAborts, AbortQuery, ac (local of AbortQuery), ReadAttributePattern (local of ReadAttributePatterns), EntryToPatterns, ParseSubrange
Doug Terry, July 16, 1992 10:21:27 am PDT
Split off from LoganQueryImpl; added several new query classes.