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; NextEntry: PROC [cursor: Cursor, dir: CursorDirection ¬ increasing] RETURNS [entry: LoganBerry.Entry] = { IF cursor.class.retrieve = NIL THEN RETURN[NIL]; Process.CheckForAbort[]; RETURN[cursor.class.retrieve[cursor, dir]]; }; EndGenerate: PROC [cursor: Cursor] RETURNS [] = { IF cursor.class.destroy = NIL THEN RETURN; cursor.class.destroy[cursor]; }; SimpleClass: QueryClass = NEW[QueryClassRec ¬ [$Simple, SimpleRetrieve, SimpleDestroy]]; SimpleCursor: TYPE = REF LoganBerry.Cursor; SimpleRetrieve: RetrieveProc = { WITH cursor.data SELECT FROM sc: SimpleCursor => RETURN[LoganBerry.NextEntry[cursor: sc­, dir: dir]]; ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Simple cursor"]; }; SimpleDestroy: DestroyProc = { 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] = { sc: SimpleCursor ¬ NEW[LoganBerry.Cursor]; sc­ ¬ LoganBerry.GenerateEntries[db: db, key: key, start: start, end: end]; cursor.class ¬ SimpleClass; cursor.data ¬ sc; }; 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 = { WITH cursor.data SELECT FROM fc: FilterCursor => { match, nothingGreater: BOOLEAN ¬ FALSE; numAtypes: INT; entry ¬ NextEntry[fc.input, dir]; UNTIL match OR entry = NIL DO 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 = { 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] = { 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; }; 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 = { 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 [] ¬ 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 = { 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] = { mc: MergerCursor ¬ NEW[MergerCursorRec]; mc.input[0] ¬ input1; mc.input[1] ¬ input2; mc.atype ¬ atype; mc.prefetch ¬ FALSE; output.class ¬ MergerClass; output.data ¬ mc; }; AntiFilterClass: QueryClass = NEW[QueryClassRec ¬ [$AntiFilter, AntiFilterRetrieve, FilterDestroy]]; AntiFilterRetrieve: RetrieveProc = { 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 match ¬ FALSE; 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] = { 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; }; 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 = { 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 { entry ¬ NextEntry[dc.input, dir]; IF entry # NIL AND NOT dc.shared.closed[other] THEN { 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 { 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 = { 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] = { 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; }; 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 = { 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 = { 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] = { udc: UnDuplicatorCursor ¬ NEW[UnDuplicatorCursorRec]; udc.input ¬ input; udc.atype ¬ atype; output.class ¬ UnDuplicatorClass; output.data ¬ udc; }; 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 ]; 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 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 = { 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 => { 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 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 = { 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] = { sc: SubtracterCursor ¬ NEW[SubtracterCursorRec]; sc.input ¬ input; sc.except ¬ except; sc.ptype ¬ ptype; sc.atype ¬ atype; output.class ¬ SubtracterClass; output.data ¬ sc; }; FeederClass: QueryClass = NEW[QueryClassRec ¬ [$Feeder, FeederRetrieve, FeederDestroy]]; FeederCursor: TYPE = REF FeederCursorRec; FeederCursorRec: TYPE = RECORD [ input: Cursor -- input cursor ]; FeederRetrieve: RetrieveProc = { 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 = { WITH cursor.data SELECT FROM fc: FeederCursor => EndGenerate[fc.input]; ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Feeder cursor"]; }; FeedEntries: PUBLIC PROC [] RETURNS [output: Cursor] = { fc: FeederCursor ¬ NEW[FeederCursorRec]; fc.input.class ¬ NIL; output.class ¬ FeederClass; output.data ¬ fc; }; SetFeederInput: PUBLIC PROC [feeder, input: Cursor] = { WITH feeder.data SELECT FROM fc: FeederCursor => fc.input ¬ input; ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Feeder cursor"]; }; 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 = { 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 = { 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] ~ { ac: AborterCursor ¬ NEW[AborterCursorRec]; ac.input ¬ input; output.class ¬ AborterClass; output.data ¬ ac; }; AbortQuery: PUBLIC PROC [cursor: Cursor] RETURNS [] ~ TRUSTED { 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. $dLoganQueryClassImpl.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. Basic operations 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 Releases the cursor; no further operations may be performed using the given cursor. This must be called once for every cursor created. $Simple query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] [cursor: Cursor] RETURNS [] Identical to the GenerateEntries operation provided by LoganBerry but returns a Cursor with class=$Simple and data=LoganBerry.Cursor. Errors: LoganBerry.NoIndex $Filter query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] could be multiple attributes of fc.atype per entry so check them all [cursor: Cursor] RETURNS [] 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. $Merger query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] so move cursor back over prefetched entry [cursor: Cursor] RETURNS [] 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. $AntiFilter query class Shares a FilterCursor record type and the DestroyProc with the $Filter class. [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] initialize match to FALSE so will return entry if no attributes of given type could be multiple attributes of fc.atype per entry so check them all 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. $Duplicator query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] Get entry off input queue Add entry to other output cursor's prefetched list Get entry off prefetched list [cursor: Cursor] RETURNS [] 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. $UnDuplicator query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] [cursor: Cursor] RETURNS [] 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. $Subtracter query class 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. Add sc.nextExceptEntry to exceptBatch list [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] 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. Discard entries from both cursors and try again [cursor: Cursor] RETURNS [] 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. $Feeder query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] [cursor: Cursor] RETURNS [] 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. Sets (or changes) the input cursor for the feeder. $Aborter query class [cursor: Cursor, dir: CursorDirection _ increasing] RETURNS [entry: LoganBerry.Entry] [cursor: Cursor] RETURNS [] 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. 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. 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. Κώ•NewlineDelimiter –(cedarcode) style˜code™Kšœ Οeœ6™BK™%K™%K˜Kšœώ™ώK™—šΟk ˜ Kšœžœ4˜AKšœžœžœ˜"Kšœ ˜ Kšœžœ ˜ K˜—K˜KšΠblœžœž˜"Kšžœ+˜2Kšžœ˜šœž œž˜K˜Kšžœžœžœ˜—headšœ™šΟn œžœ5žœ˜iKšœ ™ Kšœ7™7Kš žœžœžœžœžœ˜0K˜Kšžœ%˜+K˜—K˜š  œžœžœ˜1Kšœ‡™‡Kšžœžœžœžœ˜*Kšœ˜K˜——™Kšœžœ;˜XK™Kšœžœžœ˜+K˜šΟbœ˜ KšΠckU™Ušžœ žœž˜Kšœžœ.˜HKšžœžœ4˜D—K˜—K˜š‘ œ˜Kš’™šžœ žœž˜K˜8Kšžœžœ4˜D—K˜—K˜š  œžœžœ[žœ#žœžœ˜ΎK™…Kšœ™Kšœžœ˜*K˜KK˜K˜K˜—K™—™Kšœžœ;˜XK™Kšœžœžœ˜)šœžœžœ˜ K˜Kšœ žœ˜Kšœ žœžœ˜K˜Kšœ ˜ Kšœžœž˜K˜—K˜š‘œ˜ Kš’U™Ušžœ žœž˜šœ˜Kšœžœžœ˜'Kšœ žœ˜K˜!šžœžœ žœž˜KšœD™DK˜šžœ%žœžœž˜8šžœž˜!K˜K˜WKšžœžœžœ˜K˜—Kšžœ˜—šžœžœž˜š žœžœ žœžœž˜KKšœžœΟc&˜3—šž˜K˜!——Kšžœ˜—Kšœ˜—Kšžœžœ4˜D—K˜—K˜š‘ œ˜Kš’™šžœ žœž˜Kšœ*˜*Kšžœžœ4˜D—K˜—K˜š  œžœžœžœLžœžœžœ˜±K™ΊKšœžœ˜(šžœ žœž˜Kšžœ:˜?—K˜K˜K˜K˜K˜!K˜K˜K˜—K˜—™Kšœžœ;˜XK™Kšœžœžœ˜)šœžœžœ˜ Kšœžœžœ˜Kšœ ˜ Kšœ žœžœ£˜;Kšœ"£!˜CKšœ+£&˜QKšœ£%˜AKšœ+£˜CK˜—K˜š‘œ˜ Kš’U™Ušžœ žœž˜šœ˜Kšœ˜Kšœ˜Kšœ$˜$Kšœ žœ˜š žœ žœžœžœ£ ˜SKšœ)™)K˜0Kšœžœ˜Kšœ˜—šžœžœ žœ£˜7K˜/K˜GK˜K˜Kšœžœ˜K˜—Kšœ žœ˜#K˜.šžœžœžœ£˜@šžœ žœžœ£'˜?Kšœžœ˜—Kšžœ ˜K˜—šžœ žœžœ£˜1K˜Kšœžœ˜K˜Kšžœ˜K˜—K˜7šœ žœ*žœž˜GKšœ˜Kšœ˜Kšžœžœ˜—šžœ žœ˜K˜K˜—šžœ˜K˜K˜K˜K˜K˜—Kšœ˜—Kšžœžœ4˜D—K˜—K˜š‘ œ˜Kš’™šžœ žœž˜šœ˜Kšœ˜Kšœ˜Kšœ˜—Kšžœžœ4˜D—K˜—K˜š  œžœžœ;žœ˜pK™αKšœžœ˜(K˜K˜K˜Kšœžœ˜K˜K˜K˜——™KšœM™MK™KšœžœC˜dK˜š‘œ˜$Kš’U™Ušžœ žœž˜šœ˜Kšœžœžœ˜&Kšœ žœ˜K˜!š žœžœžœ žœž˜!Kšœ£0™MKšœžœ˜KšœD™DK˜šžœ%žœžœž˜8šžœž˜!K˜K˜WKšžœžœžœ˜K˜—Kšžœ˜—šžœž˜ K˜!—Kšžœ˜—Kšœ˜—Kšžœžœ4˜D—K˜—K˜š  œžœžœžœ6žœ˜ŽK™•Kšœžœ˜(šžœ žœž˜Kšžœ:˜?—K˜K˜K˜K˜Kšœ žœ˜K˜K˜K˜—K˜—™KšœžœG˜hK™Kšœžœžœ˜1šœžœžœ˜$Kšœ£˜Kšœ£˜+Kšœ£%˜AK˜—Kšœžœžœ˜7šœžœžœ˜'Kš œžœžœžœžœžœ£˜CKšœ žœžœžœžœžœžœ£#˜dKšœ˜—K˜šΠbnœ˜$Kš’U™Ušžœ žœž˜šœ˜Kšœžœ žœžœ˜*šžœ žœžœ˜,K™K˜!š žœ žœžœžœžœ˜5K™2šžœžœ˜%šžœ˜Kšœžœ˜*K˜—šžœ˜Jšœžœžœ0˜>Kšžœžœžœžœ˜6Kšœ žœ˜K˜——K˜—K˜—šžœ˜K™K˜+K˜AK˜—Kšœ˜—Kšžœžœ8˜H—K˜—K˜š‘œ˜"Kš’™šžœ žœž˜šœ˜Kšœžœ˜ šžœžœž˜3Kšœ˜—Kšœ˜—Kšžœžœ8˜H—K˜—K˜š œž œžœ˜TK™ΝKšœ˜Kšœžœ˜6Kšœžœ˜Kšœžœ˜Kšœžœ˜K˜ K˜K˜K˜ K˜Kšœžœ˜K˜ K˜K˜K˜ K˜K˜——™Kšœ žœM˜pK™Kšœžœžœ˜5šœžœžœ˜&Kšœ£˜Kšœ ˜ Kšœ"žœ£˜BK˜—K˜š€œ˜&Kš’U™Ušžœ žœž˜šœ˜Kšœ!˜!šžœ£˜K˜"K˜2Kšžœ žœžœžœžœžœžœ˜CKšžœ˜—K˜Kšœ˜—Kšžœžœ:˜J—K˜—K˜š‘œ˜$Kš’™šžœ žœž˜Kšœ2˜2Kšžœžœ:˜J—K˜—K˜š œž œ2žœ˜mK™’Kšœžœ˜5K˜K˜K˜!K˜K˜——™KšœžœG˜hK™Kšœžœžœ˜1šœžœžœ˜$Kšœ£˜Kšœ£ ˜Kšœ!£˜/Kšœ!£)˜JKšœ$žœ£$˜NKšœ žœ£ ˜EKšœ žœžœžœ£"˜PKšœ$žœ£/˜XK˜—KšœΛ™ΛK˜š  œžœ.žœ˜bK˜$Kšœžœ˜Kšœžœ˜šžœžœžœ˜K˜+Kš žœžœžœžœžœ˜)K˜—K˜>K˜/š žœžœžœžœ£˜PKšœžœžœžœ˜%šžœNžœž˜\Kšœ*™*šžœžœžœ˜Kšœžœ˜&K˜K˜—Kšœ žœ˜%K˜K˜/Kšžœ˜—Kšœ˜—Kšžœ˜K˜—K˜š œžœ3žœ˜WKšœ žœžœ˜$šžœžœžœ˜Kšžœžœžœ˜6Kšžœ˜K˜—Kšœžœ˜ K˜š žœžœžœžœž˜.K˜ K˜Kšžœ˜—šžœžœžœ˜šžœžœ˜šžœ˜K˜%K˜&Kšœ˜—šžœ˜K˜K˜——K˜—K˜K˜—š€œ˜$Kš’U™Ušžœ žœž˜šœ˜šžœžœž˜K˜$—K˜!šžœžœžœ£/˜NK˜Ršžœ&žœž˜7Kšœžœ£˜šœ £˜)Kšœžœ˜šžœžœž˜K˜.šœ˜Kšœ5˜5K˜AK˜7Kšžœžœ˜:K˜—šžœ˜Kšœ€™€K˜Rš žœžœžœ/žœ žœž˜UK˜YKšžœžœžœ˜?Kšžœ˜—Kšœ˜——Kš žœ žœžœžœ£˜*K™/K˜!Kšœ˜šžœžœž˜K˜$—K˜—šœ£%˜3K˜$K˜—Kšžœ˜—Kšžœ˜—Kšœ˜—Kšžœžœ8˜H—K˜—K˜š‘œ˜"Kš’™šžœ žœž˜Kšœ.˜.Kšžœžœ8˜H—K˜—K˜š œž œAžœ˜yK™ΤKšœžœ˜0K˜K˜K˜K˜K˜K˜K˜——™Kšœžœ;˜XK™Kšœžœžœ˜)šœžœžœ˜ Kšœ£˜K˜—K˜š€œ˜ Kš’U™Ušžœ žœž˜šœ˜Kšœžœ˜ šžœžœžœ˜K˜!—Kšœ˜—Kšžœžœ4˜D—K˜—K˜š‘ œ˜Kš’™šžœ žœž˜Kšœ*˜*Kšžœžœ4˜D—K˜—K˜š  œž œžœ˜8K™§Kšœžœ˜(Kšœžœ˜K˜K˜K˜K˜—š œž œ˜7Kšœ2™2šžœ žœž˜K˜%Kšžœžœ4˜D—K˜——™Kšœžœ>˜\K™Kšœžœžœ˜+šœžœžœ˜!Kšœ£˜Kšœ žœžœ£'˜BKšœ žœžœ£1˜IK˜—K˜š‘œ˜!Kš’U™UKšžœžœžœ ˜šžœ žœž˜šœ˜Kšžœ žœžœ ˜ Kšžœžœžœ˜@K˜!Kšœ žœ˜K˜—Kšžœžœ5˜E—šž˜Kšœ žœžœ˜—K˜—K˜š‘œ˜Kš’™šžœ žœž˜Kšœ+˜+Kšžœžœ5˜E—K˜—K˜š  œžœžœžœ˜FK™ΔKšœžœ˜*K˜K˜K˜K˜K˜—š   œžœžœžœžœ˜?Kšœ£™£Kšœh™hšžœ žœž˜šœ˜Kšœ žœ˜šžœžœž˜Kšœ5žœ˜?—K˜—Kšžœžœ˜—K˜——K˜Kšžœ˜™*K™Kšœ ΟrΤ™ΰ—™+Kšœ ₯œ₯§™Ω—™,Kšœ ₯ω™…—™-Kšœ ₯™+—™,Kšœ ₯œ₯ ™5—™+Kšœ ₯.™:—™-Kšœ ₯œ™(—™)Kšœ ₯™—™,K™*Kšœ ₯™)—™*K™UKšœ ₯—œ₯œ₯ ™ψ—™*K™Kš œ ₯.œ₯%œ₯Dœ₯.œ₯œ!₯ ™—™)K™?—K™—…—@Μz.