The factory scheme can be used to model a volcano-style query processor. Each node in the query tree is an iterator that calls upon the operands to produce a chunk, which are combined into a new chunk for consumption of the parent. The prototypical join(R,S) query illustrates it. The plan does not test for all boundary conditions, it merely implements a nested loop. The end of a sequence is identified by a NIL chunk.
factory query(); Left:= sql.bind("relationA"); Right:= sql.bind("relationB"); rc:= sql.joinStep(Left,Right); barrier rc!= nil; io.print(rc); rc:= sql.joinStep(Left,Right); redo rc!= nil; exit rc; end query; #nested loop join factory sql.joinStep(Left:bat[:any,:any],Right:bat[:any,:any]):bat[:any,:any]; lc:= bat.chunkStep(Left); barrier outer:= lc != nil; rc:= bat.chunkStep(Right); barrier inner:= rc != nil; chunk:= algebra.join(lc,rc); yield chunk; rc:= bat.chunkStep(Right); redo inner:= rc != nil; exit inner; lc:= bat.chunkStep(Left); redo outer:= lc != nil; exit outer; # we have seen everything return nil; end joinStep; #factory for left branch factory chunkStepL(L:bat[:any,:any]):bat[:any,:any]; i:= 0; j:= 20; cnt:= algebra.count(L); barrier outer:= j<cnt; chunk:= algebra.slice(L,i,j); i:= j; j:= i+ 20; yield chunk; redo loop:= j<cnt; exit outer; # send last portion chunk:= algebra.slice(L,i,cnt); yielD chunk; return nil; end chunkStep; #factory for right leg factory chunkStepR(L:bat[:any,:any]):bat[:any,:any];
So far we haven't re-used the pattern that both legs are identical. This could be modeled by a generic chunk factory. Choosing a new factory for each query steps reduces the administrative overhead.
An area where factories might be useful are support for materialized views, i.e. the result of a query is retained for ease of access. A simple strategy is to prepare the result once and return it on each successive call. Provided the arguments have not been changed. For example:
factory view1(l:int, h:int):bat[:oid,:str]; a:bat[:oid,:int]:= bbp.bind("emp","age"); b:bat[:oid,:str]:= bbp.bind("emp","name"); barrier always := true; lOld := l; hOld := h; c := algebra.select(a,l,h); d := algebra.semijoin(b,c); barrier available := true; yield d; leave available := calc.!=(lOld,l); leave available := calc.!=(hOld,h); redo available := true; exit available; redo always; exit always; end view1;
The code should be extended to also check validity of the BATs. It requires a check against the last transaction identifier known.
The Factory concept is still rather experimental and many questions should be considered, e.g. What is the lifetime of a factory? Does it persists after all clients has disappeared? What additional control do you need? Can you throw an exception to a Factory?