QueryNodeRegister.mesa
Copyright Ó 1985, 1986 by Xerox Corporation. All rights reserved.
Doug Terry, July 28, 1987 11:11:29 am PDT
This allows active Tioga documents that retrieve nodes from a LoganBerry database as they are requested. Nodes with the value "LBQuery" for the "Active" property are dynamically replaced with the results obtained from running the query specified by the node's contents. The query need not always be run in its entirety; in many cases the query node can be replaced by two nodes: the first being the first database entry returned by the query and the second being a new query (specifcally a database cursor) to generate the remaining entries.
DIRECTORY
IO USING [atom, BreakProc, EndOfStream, GetTokenRope, IDProc, PutFR, RIS, rope, STREAM],
LoganBerry USING [AttributeType, AttributeValue, Entry, Error, Open],
LoganQuery USING [AttributePatterns, ComplexCursor, NextEntry, QueryEntries, ReadAttributePatterns, SyntaxError],
NodeProps USING [GetProp, PutProp],
Rope USING [Cat, Concat, ROPE, Size],
TextLooks USING [CreateRun],
TextNode USING [Body, Ref],
TextNodeRegistry USING [ActivityProc, ProcSetRec, Register, SizeProc, TransformProc];
QueryNodeRegister: CEDAR MONITOR
IMPORTS IO, LoganBerry, LoganQuery, NodeProps, Rope, TextLooks, TextNodeRegistry
= BEGIN
ROPE: TYPE ~ Rope.ROPE;
Ref: TYPE ~ TextNode.Ref;
remember last query and its cursor to improve performance
lastq: Ref ← NIL;
cursor: REF LoganQuery.ComplexCursor ← NIL;
Note: There should be a timeout on QueryToText to avoid long waits.
FormatProc: TYPE ~ PROC [entry: LoganBerry.Entry] RETURNS [rope: ROPE];
QueryToText: ENTRY PROC [q, p, s: Ref ← NIL, fproc: FormatProc] RETURNS [t: Ref] ~ {
On input:
q should be a node with Active property $LBQuery,
p (if not NIL) should be q's parent,
s (if not NIL) should be q's preceeding sibling,
Note that p and s can be determined given q so their input is only for performance improvement.
Upon completion:
t is a text node containing the first database entry obtained by running the query specified in q (or NIL if no such entry),
t.next is a $LBQuery node representing the rest of the query that remains to be run.
ENABLE LoganBerry.Error => {
q.rope ← IO.PutFR["%g\n%g - %g", IO.rope[q.rope], IO.atom[ec], IO.rope[explanation]];
q.runs ← TextLooks.CreateRun[Rope.Size[q.rope]];
GOTO QueryCompleted
};
entry: LoganBerry.Entry;
IF q=NIL THEN RETURN[NIL];
Get next entry returned by query
IF q # lastq THEN { -- get new cursor
lastq ← q;
cursor ← NARROW[NodeProps.GetProp[q, $LBCursor]];
IF cursor = NIL THEN
cursor ← QueryToCursor[q];
IF cursor = NIL THEN GOTO QueryCompleted;
};
entry ← LoganQuery.NextEntry[cursor: cursor^];
IF entry = NIL THEN GOTO QueryCompleted;
Create new node t for returned entry
t ← NEW[TextNode.Body ← q^]; -- t inherits q's properties
t.child ← NIL;
t.last ← FALSE;
t.rope ← fproc[entry];
t.runs ← TextLooks.CreateRun[Rope.Size[t.rope]];
Link t before q in tree
t.next ← q;
IF s=NIL THEN {
IF p=NIL THEN {
p ← q;
WHILE NOT p.last DO p ← p.next; ENDLOOP;
p ← p.next; -- p is now q's parent
};
IF p.child#q THEN {
s ← p.child; -- get to first sibling
WHILE s.next # q DO s ← s.next; ENDLOOP;
};
};
IF s#NIL
THEN s.next ← t
ELSE p.child ← t;
EXITS
QueryCompleted => {
NodeProps.PutProp[q, $Active, NIL]; -- make node inactive
RETURN[NIL];
};
};
QueryToCursor: PROC [q: Ref] RETURNS [cursor: REF LoganQuery.ComplexCursor] ~ {
query: ROPE = q.rope;
cursor ← InitiateQuery[query];
q.rope ← IF cursor = NIL THEN Rope.Concat["Bad Query: ", query] ELSE NIL;
q.runs ← TextLooks.CreateRun[Rope.Size[q.rope]];
NodeProps.PutProp[q, $LBCursor, cursor];
};
InitiateQuery: PROC [rope: Rope.ROPE] RETURNS [c: REF LoganQuery.ComplexCursor] ~ {
dbname: Rope.ROPE;
ap: LoganQuery.AttributePatterns;
s: IO.STREAMIO.RIS[rope];
dbname ← IO.GetTokenRope[s, IO.IDProc ! IO.EndOfStream => GOTO End].token;
ap ← LoganQuery.ReadAttributePatterns[s ! LoganQuery.SyntaxError => GOTO End];
c ← NEW[LoganQuery.ComplexCursor ← LoganQuery.QueryEntries[db: LoganBerry.Open[dbName: dbname], patterns: ap].cursor];
EXITS
End => RETURN[NIL];
};
Active=LBQuery
EntryToRope: FormatProc ~ {
[entry: LoganBerry.Entry] RETURNS [rope: ROPE]
rope ← NIL;
FOR l: LoganBerry.Entry ← entry, l.rest WHILE l # NIL DO
rope ← IO.PutFR["%g%g: %g\n", IO.rope[rope], IO.atom[l.first.type], IO.rope[l.first.value]];
ENDLOOP;
};
LBQueryTransform: TextNodeRegistry.TransformProc = {
[node: TextNode.Ref, parent: TextNode.Ref, wantFirst: BOOLEAN, clientData: REF ANY] RETURNS [new: TextNode.Ref]
new ← QueryToText[node, parent, NIL, EntryToRope];
IF new=NIL THEN RETURN[node];
IF NOT wantFirst THEN {
the query specified in q must be completely processed; new will be the last node in the list of retrieved database entries.
last: Ref ← new;
WHILE last # NIL DO
new ← last;
last ← QueryToText[node, parent, last, EntryToRope];
ENDLOOP;
};
};
LBQuerySize: TextNodeRegistry.SizeProc = {
[node: TextNode.Ref, clientData: REF ANY] RETURNS [size: INT]
RETURN[300]; -- a *rough* estimate
};
Active=WPQuery
tName: LoganBerry.AttributeType = $name;
tRname: LoganBerry.AttributeType = $rname;
tOffice: LoganBerry.AttributeType = $officenumber;
tHome: LoganBerry.AttributeType = $homenumber;
tRemarks: LoganBerry.AttributeType = $remarks;
LBToTDirEntry: FormatProc ~ {
[entry: LoganBerry.Entry] RETURNS [rope: ROPE]
GetAttributeValue: PROC [entry: LoganBerry.Entry, type: LoganBerry.AttributeType] RETURNS [LoganBerry.AttributeValue] ~ {
FOR e: LoganBerry.Entry ← entry, e.rest WHILE e # NIL DO
IF e.first.type = type THEN
RETURN[e.first.value];
ENDLOOP;
RETURN[NIL];
};
val: Rope.ROPE;
rope ← GetAttributeValue[entry, tName];
val ← GetAttributeValue[entry, tRname];
IF val # NIL THEN
rope ← Rope.Cat[rope, " <", val, ">"];
val ← GetAttributeValue[entry, tOffice];
rope ← Rope.Cat[rope, "\t", IF val = NIL THEN "*" ELSE val];
val ← GetAttributeValue[entry, tHome];
rope ← Rope.Cat[rope, "\t", IF val = NIL THEN "*" ELSE val];
val ← GetAttributeValue[entry, tRemarks];
IF val # NIL THEN
rope ← Rope.Cat[rope, "\t", val];
};
WPQueryTransform: TextNodeRegistry.TransformProc = {
[node: TextNode.Ref, parent: TextNode.Ref, wantFirst: BOOLEAN, clientData: REF ANY] RETURNS [new: TextNode.Ref]
new ← QueryToText[node, parent, NIL, LBToTDirEntry];
IF new=NIL THEN RETURN[node];
IF NOT wantFirst THEN {
the query specified in q must be completely processed; new will be the last node in the list of retrieved database entries.
last: Ref ← new;
WHILE last # NIL DO
new ← last;
last ← QueryToText[node, parent, last, LBToTDirEntry];
ENDLOOP;
};
};
WPQuerySize: TextNodeRegistry.SizeProc = {
[node: TextNode.Ref, clientData: REF ANY] RETURNS [size: INT]
RETURN[300]; -- a *rough* estimate
};
TextNodeRegistry.Register[activity: $LBQuery, procs: NEW[TextNodeRegistry.ProcSetRec ← [activityOn: TRUE, transformProc: LBQueryTransform, sizeProc: LBQuerySize, activityProc: NIL]]];
TextNodeRegistry.Register[activity: $WPQuery, procs: NEW[TextNodeRegistry.ProcSetRec ← [activityOn: TRUE, transformProc: WPQueryTransform, sizeProc: WPQuerySize, activityProc: NIL]]];
END.